diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..66406bc
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,2 @@
+( (erlang-mode . ((indent-tabs-mode . nil)) )
+ )
diff --git a/.travis.yml b/.travis.yml
index 075b529..114b3ef 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,13 @@
+sudo: false
language: erlang
script: "make ci"
otp_release:
+ - 17.4
+ - 17.3
- 17.1
- 17.0
+ - R16B03-1
+ - R16B03
- R16B02
- R16B01
- R16B
diff --git a/Makefile b/Makefile
index 7f8207d..44b3b96 100644
--- a/Makefile
+++ b/Makefile
@@ -43,4 +43,5 @@ clean_plt:
rm -f $(EXOMETER_PLT)
dialyzer: deps compile $(EXOMETER_PLT)
- dialyzer -r ebin --plt $(EXOMETER_PLT) $(DIALYZER_OPTS)
+ dialyzer -r ebin --plt $(EXOMETER_PLT) $(DIALYZER_OPTS) | \
+ fgrep -v -f ./dialyzer.ignore-warnings
diff --git a/README.md b/README.md
index 5ac2b48..37566a3 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved.
-__Version:__ Dec 3 2014 19:51:33
+__Version:__ Apr 17 2015 14:08:02
__Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)), Magnus Feuer ([`magnus.feuer@feuerlabs.com`](mailto:magnus.feuer@feuerlabs.com)).
@@ -474,6 +474,13 @@ Exometer Core defaults can be changed either through OTP application environment
variables or through the use of Basho's `cuttlefish`
([`https://github.com/basho/cuttlefish`](https://github.com/basho/cuttlefish)).
+__Note:__ Exometer Core will check both the `exometer` and the `exometer_core`
+application environments. The `exometer` environment overrides the
+`exometer_core` environment. However, if only `exometer_core` is used, any
+`exometer` environment will simply be ignored. This is because of the
+application controller: environment data is not loaded until the application
+in question is loaded.
+
#### Configuring type - entry maps ####
@@ -734,6 +741,7 @@ processing is complete.
exometer_proc |
exometer_report |
exometer_report_lager |
+exometer_report_logger |
exometer_report_tty |
exometer_shallowtree |
exometer_slide |
diff --git a/dialyzer.ignore-warnings b/dialyzer.ignore-warnings
new file mode 100644
index 0000000..a5a2adb
--- /dev/null
+++ b/dialyzer.ignore-warnings
@@ -0,0 +1,2 @@
+exometer.erl:209: The variable _ can never match since previous clauses completely covered the type 'enabled'
+exometer.erl:444: The variable _ can never match since previous clauses completely covered the type 'enabled'
diff --git a/doc/README.md b/doc/README.md
index 9c1c4e4..e212344 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -4,7 +4,7 @@
Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved.
-__Version:__ Dec 3 2014 19:51:33
+__Version:__ Apr 17 2015 14:08:02
__Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)), Magnus Feuer ([`magnus.feuer@feuerlabs.com`](mailto:magnus.feuer@feuerlabs.com)).
@@ -474,6 +474,13 @@ Exometer Core defaults can be changed either through OTP application environment
variables or through the use of Basho's `cuttlefish`
([`https://github.com/basho/cuttlefish`](https://github.com/basho/cuttlefish)).
+__Note:__ Exometer Core will check both the `exometer` and the `exometer_core`
+application environments. The `exometer` environment overrides the
+`exometer_core` environment. However, if only `exometer_core` is used, any
+`exometer` environment will simply be ignored. This is because of the
+application controller: environment data is not loaded until the application
+in question is loaded.
+
#### Configuring type - entry maps ####
@@ -734,6 +741,7 @@ processing is complete.
exometer_proc |
exometer_report |
exometer_report_lager |
+exometer_report_logger |
exometer_report_tty |
exometer_shallowtree |
exometer_slide |
diff --git a/doc/edoc-info b/doc/edoc-info
index e671710..a78b3ee 100644
--- a/doc/edoc-info
+++ b/doc/edoc-info
@@ -5,6 +5,6 @@
exometer_cpu,exometer_duration,exometer_entry,exometer_folsom,
exometer_folsom_monitor,exometer_function,exometer_histogram,
exometer_igor,exometer_info,exometer_probe,exometer_proc,
- exometer_report,exometer_report_lager,exometer_report_tty,
- exometer_shallowtree,exometer_slide,exometer_slot_slide,
- exometer_spiral,exometer_uniform,exometer_util]}.
+ exometer_report,exometer_report_lager,exometer_report_logger,
+ exometer_report_tty,exometer_shallowtree,exometer_slide,
+ exometer_slot_slide,exometer_spiral,exometer_uniform,exometer_util]}.
diff --git a/doc/exometer.md b/doc/exometer.md
index 2b3765b..04f9806 100644
--- a/doc/exometer.md
+++ b/doc/exometer.md
@@ -181,7 +181,7 @@ value() = any()
## Function Index ##
-
+
@@ -337,6 +337,13 @@ get_value(Name::name(), DataPoint::
+
+### global_status/1 ###
+
+`global_status(St) -> any()`
+
+
### info/1 ###
diff --git a/doc/exometer_admin.md b/doc/exometer_admin.md
index 87a82a7..6b1f50d 100644
--- a/doc/exometer_admin.md
+++ b/doc/exometer_admin.md
@@ -11,7 +11,7 @@ __Behaviours:__ [`gen_server`](gen_server.md).
+Name
.handle_call/3 | |
handle_cast/2 | |
handle_info/2 | |
init/1 | |
load_defaults/0 | |
load_predefined/0 | |
make_patterns/2 | |
monitor/2 | |
monitor/3 | |
new_entry/3 | |
normalize_name/1 | |
prefixes/1 | |
preset_defaults/0 | |
propose/3 | |
re_register_entry/3 | |
register_application/1 | |
repair_entry/1 | |
set_default/3 | Sets a default definition for a metric type, possibly using wildcards. |
start_link/0 | |
terminate/2 | |
@@ -69,7 +69,7 @@ Convenience function for testing which template will apply to
### handle_call/3 ###
-`handle_call(X1, From, S) -> any()`
+`handle_call(Req, From, S) -> any()`
@@ -121,6 +121,13 @@ Convenience function for testing which template will apply to
`monitor(Name, Pid) -> any()`
+
+
+### monitor/3 ###
+
+`monitor(Name, Pid, OnError) -> any()`
+
+
### new_entry/3 ###
diff --git a/doc/exometer_alias.md b/doc/exometer_alias.md
index ddba74e..1246da5 100644
--- a/doc/exometer_alias.md
+++ b/doc/exometer_alias.md
@@ -128,13 +128,20 @@ stat_map() = [{name(), [{dp(), <
## Function Index ##
-delete/1 | Delete an alias, if it exists in the registry. |
get_value/1 | Resolve the given alias and return corresponding metric and value. |
load/1 | Load a list of mappings between entry+datapoint pairs and aliases. |
new/3 | Create a new alias. |
prefix_foldl/3 | Fold (ascending order) over the aliases matching Prefix . |
prefix_foldr/3 | Fold (descending order) over the aliases matching Prefix . |
prefix_match/1 | List all aliases matching the given prefix. |
regexp_foldl/3 | Fold (ascending order) over the aliases matching Regexp . |
regexp_foldr/3 | Fold (descending order) over the aliases matching Regexp . |
resolve/1 | Look up an alias in the registry and return corresponding mapping. |
reverse_map/2 | List all aliases mapped to the given entry+datapoint pair(s). |
start_link/0 | |
unload/1 | Unload a list of mappings. |
update/2 | Resolves the given alias and updates the corresponding entry (if any). |
+check_map/1 | |
delete/1 | Delete an alias, if it exists in the registry. |
get_value/1 | Resolve the given alias and return corresponding metric and value. |
load/1 | Load a list of mappings between entry+datapoint pairs and aliases. |
new/3 | Create a new alias. |
prefix_foldl/3 | Fold (ascending order) over the aliases matching Prefix . |
prefix_foldr/3 | Fold (descending order) over the aliases matching Prefix . |
prefix_match/1 | List all aliases matching the given prefix. |
regexp_foldl/3 | Fold (ascending order) over the aliases matching Regexp . |
regexp_foldr/3 | Fold (descending order) over the aliases matching Regexp . |
resolve/1 | Look up an alias in the registry and return corresponding mapping. |
reverse_map/2 | List all aliases mapped to the given entry+datapoint pair(s). |
start_link/0 | |
unload/1 | Unload a list of mappings. |
update/2 | Resolves the given alias and updates the corresponding entry (if any). |
## Function Details ##
+
+
+### check_map/1 ###
+
+`check_map(Map) -> any()`
+
+
### delete/1 ###
diff --git a/doc/exometer_cache.md b/doc/exometer_cache.md
index f88e802..b679075 100644
--- a/doc/exometer_cache.md
+++ b/doc/exometer_cache.md
@@ -10,7 +10,7 @@ __Behaviours:__ [`gen_server`](gen_server.md).
## Function Index ##
-
+
@@ -31,6 +31,13 @@ __Behaviours:__ [`gen_server`](gen_server.md).
`delete(Name, DataPoint) -> any()`
+
+
+### delete_name/1 ###
+
+`delete_name(Name) -> any()`
+
+
### handle_call/3 ###
diff --git a/doc/exometer_overview.odp b/doc/exometer_overview.odp
new file mode 100644
index 0000000..1b9636e
Binary files /dev/null and b/doc/exometer_overview.odp differ
diff --git a/doc/exometer_overview.png b/doc/exometer_overview.png
new file mode 100644
index 0000000..cedd5e9
Binary files /dev/null and b/doc/exometer_overview.png differ
diff --git a/doc/exometer_probe.md b/doc/exometer_probe.md
index cb0a36f..8f3582b 100644
--- a/doc/exometer_probe.md
+++ b/doc/exometer_probe.md
@@ -603,15 +603,53 @@ process when it receives a message that is not recognized by the
internal receive loop.
+
The implementation of `probe_handle_msg/2` shall return `{ok,
NewState}`, where `NewState` contains the new state of the probe
that reflects the processed message.
+
+
+
+
+### Fault tolerance ###
+
+
+Probes are supervised by the `exometer_admin` process, and can be restarted
+after a crash. Restart parameters are provided via the option
+`{restart, Params}`, where `Params` is a list of `{Frequency, Action}`
+tuples. `Frequency` is either `{Count, MilliSecs}` or `'_`', and
+the corresponding `Action :: restart | disable | delete` will be performed
+if the frequency of restarts falls within the given limit.
+
+
+
+For example, `[{{3, 1000}, restart}]` will allow 3 restarts within a 1-second
+window. The matching is performed from top to bottom, and the first matching
+pattern is acted upon. If no matching pattern is found, the default action
+is `delete`. A pattern `{'_', Action}` acts as a catch-all, and should
+be put last in the list.
+
+
+
+It is also possible to specify `{{Count, '_'}, Action}`, which means
+that a total of `Count` restarts is permitted, regardless of how far apart
+they are. The count is reset after each restart.
+
+
+Example:
+
+```erlang
+
+ {restart, [{{3,1000}, restart}, % up to 3 restarts within 1 sec
+ {{4,'_'} , disable}, % a total of up to 4 restarts
+ {'_' , delete}]} % anything else
+```
## Function Index ##
-
+
@@ -667,6 +705,13 @@ that reflects the processed message.
`reset(Name, Type, Pid) -> any()`
+
+
+### restart/4 ###
+
+`restart(Name, Module, Error, SpawnOpts) -> any()`
+
+
### sample/3 ###
diff --git a/doc/exometer_proc.md b/doc/exometer_proc.md
index 770cd09..a4477ce 100644
--- a/doc/exometer_proc.md
+++ b/doc/exometer_proc.md
@@ -37,7 +37,7 @@ following messages:
## Function Index ##
-
+
@@ -130,6 +130,13 @@ Spawn an `exometer_proc` process.
This function sets up appropriate monitoring, and calls the function `F`
which needs to initialize the probe and enter an event loop.
(Note: `exometer_proc` processes are responsible for their own event loop).
+
+
+### spawn_process/3 ###
+
+`spawn_process(Name, F, Opts) -> any()`
+
+
### stop/0 ###
diff --git a/doc/exometer_report.md b/doc/exometer_report.md
index 7a4cf40..2d08295 100644
--- a/doc/exometer_report.md
+++ b/doc/exometer_report.md
@@ -369,7 +369,7 @@ time_ms() = pos_integer()
add_reporter/2 | Add a reporter. |
call_reporter/2 | Send a custom (synchronous) call to Reporter . |
cast_reporter/2 | Send a custom (asynchronous) cast to Reporter . |
delete_interval/2 | Delete a named interval. |
disable_me/2 | Used by a reporter to disable itself. |
disable_reporter/1 | Disable Reporter . |
enable_reporter/1 | Enable Reporter . |
get_intervals/1 | List the named intervals for Reporter . |
list_metrics/0 | Equivalent to list_metrics([]). |
list_metrics/1 | List all metrics matching Path , together with subscription status. |
list_reporters/0 | List the name and pid of each known reporter. |
list_subscriptions/1 | List all subscriptions for Reporter . |
new_entry/1 | Called by exometer whenever a new entry is created. |
remove_reporter/1 | Remove reporter and all its subscriptions. |
remove_reporter/2 | Remove Reporter (non-blocking call). |
restart_intervals/1 | Restart all named intervals, respecting specified delays. |
set_interval/3 | Specify a named interval. |
setopts/3 | Called by exometer when options of a metric entry are changed. |
start_link/0 | Starts the server
--------------------------------------------------------------------. |
start_reporters/0 | |
subscribe/4 | Equivalent to subscribe(Reporter, Metric, DataPoint, Interval, [],
false). |
subscribe/5 | Equivalent to subscribe(Reporter, Metric, DataPoint, Interval, Extra,
-false). |
subscribe/6 | Add a subscription to an existing reporter. |
terminate_reporter/1 | |
unsubscribe/3 | Equivalent to unsubscribe(Reporter, Metric, DataPoint, []). |
unsubscribe/4 | Removes a subscription. |
unsubscribe_all/2 | Removes all subscriptions related to Metric in Reporter. |
+false).subscribe/6 | Add a subscription to an existing reporter. |
terminate_reporter/1 | |
trigger_interval/2 | Trigger a named interval. |
unsubscribe/3 | Equivalent to unsubscribe(Reporter, Metric, DataPoint, []). |
unsubscribe/4 | Removes a subscription. |
unsubscribe_all/2 | Removes all subscriptions related to Metric in Reporter. |
@@ -412,12 +412,19 @@ subscriptions to the reporter.
`{intervals, [named_interval()]}`
named_interval() :: {Name::atom(), Interval::pos_integer()}
| {Name::atom(), Interval::time_ms(), delay()::time_ms()}
+| {Name::atom(), 'manual'}
Define named intervals. The name can be used by subscribers, so that all
subsriptions for a given named interval will be reported when the interval
triggers. An optional delay (in ms) can be given: this will cause the first
interval to start in `Delay` milliseconds. When all intervals are named
at the same time, the delay parameter can be used to achieve staggered
-reporting.
+reporting. If the interval is specified as
+```
+ 'manual
+```
+', it will have
+to be triggered manually using [`trigger_interval/2`](#trigger_interval-2).
+
### call_reporter/2 ###
@@ -656,10 +663,16 @@ set_interval(Reporter::reporter_name(), Name::
Specify a named interval.
-
See [`add_reporter/2`](#add_reporter-2) for a description of named intervals.
The named interval is here specified as either `Time` (milliseconds) or
-`{Time, Delay}`, where a delay in milliseconds is provided.
+`{Time, Delay}`, where a delay in milliseconds is provided. It is also
+specify an interval as
+```
+ 'manual
+```
+
+', indicating that the interval can
+only be triggered manually via [`trigger_interval/2`](#trigger_interval-2).
If the named interval exists, it will be replaced with the new definition.
@@ -765,6 +778,28 @@ even if the metric cannot be read.
`terminate_reporter(Reporter) -> any()`
+
+
+### trigger_interval/2 ###
+
+
+
+trigger_interval(Reporter::reporter_name(), Name::atom()) -> ok
+
+
+
+
+Trigger a named interval.
+
+
+This function is mainly used to trigger intervals defined as
+```
+ 'manual
+```
+',
+but can be used to trigger any named interval. If a named interval with
+a specified time in milliseconds is triggered this way, it will effectively
+be restarted, and will repeat as usual from that point on.
### unsubscribe/3 ###
diff --git a/doc/exometer_report_logger.md b/doc/exometer_report_logger.md
new file mode 100644
index 0000000..696742e
--- /dev/null
+++ b/doc/exometer_report_logger.md
@@ -0,0 +1,271 @@
+
+
+# Module exometer_report_logger #
+* [Description](#description)
+* [Data Types](#types)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+Exometer report collector and logger.
+__Behaviours:__ [`gen_server`](gen_server.md).
+
+__This module defines the `exometer_report_logger` behaviour.__
Required callback functions: `logger_init_input/1`, `logger_init_output/1`, `logger_handle_data/2`.
+
+
+## Description ##
+
+
+
+This module implements a behavior for collecting reporting data and
+handling it (logging to disk or ets, printing to tty, etc.)
+
+
+
+The logger has built-in support for receiving input via UDP, TCP or
+internal Erlang messaging, as well as a plugin API for custom input
+handling. Correspondingly, it has support for output to TTY or ets, as
+well as a plugin API for custom output.
+
+
+
+An example of how the logger can be used can be found in
+`test/exometer_test_udp_reporter.erl`, which implements a UDP-based
+reporter as well as an input plugin and an output plugin. This reporter
+is used by `test/exometer_report_SUITE.erl`.
+
+
+
+Loggers can be combined, e.g. by creating one logger that receives Erlang
+messages, and other loggers that receive from different sources, prefix
+their reports and pass them on to the first logger.
+
+
+
+
+## Input plugins ##
+
+
+
+An input plugin is initiated by `Module:logger_init_input(State)`, where
+`State` is whatever was given as a `state` option (default: `undefined`).
+The function must create a process and return `{ok, Pid}`. `Pid` is
+responsible for setting up whatever input channel is desired, and passes
+on incoming data to the logger via Erlang messages `{plugin, Pid, Data}`.
+
+
+
+
+## Output Chaining ##
+
+
+
+Each incoming data item is passed through the list of output operators.
+Each output operator is able to modify the data (the `tty` and `ets`
+operators leave the data unchanged). Output plugins receive the data
+in `Module:logger_handle_data(Data, State)`, which must return
+`{NewData, NewState}`. The state is private to the plugin, while `NewData`
+will be passed along to the next output operator.
+
+
+
+
+## Flow control ##
+
+
+
+The logger will handle flow control automatically for `udp` and `tcp`
+inputs. If `{active,once}` or `{active, false}`, the logger will trigger
+`{active, once}` each time it has handled an incoming message.
+If `{active, N}`, it will "refill" the port each time it receives an
+indication that it has become passive.
+
+
+Input plugins create a process in `Module:logger_init_input/1`. This process
+can mimick the behavior of Erlang ports by sending a `{plugin_passive, Pid}`
+message to the logger. The logger will reply with a message,
+`{plugin_active, N}`, where `N` is the value given by the `active` option.
+
+
+## Data Types ##
+
+
+
+
+### logger_info() ###
+
+
+
+
+logger_info() = {id, any()} | {input, proplist()} | {output, proplist()}
+
+
+
+
+
+
+### proplist() ###
+
+
+
+
+proplist() = [{atom(), any()}]
+
+
+
+
+
+## Function Index ##
+
+
+info/0 | List active logger instances. |
info/1 | Lists the settings of a given logger instance. |
new/1 | Create a new logger instance. |
start_link/1 | Start function for logger instance. |
+
+
+
+
+## Function Details ##
+
+
+
+### info/0 ###
+
+
+
+info() -> [{pid(), [logger_info()]}]
+
+
+
+
+List active logger instances.
+
+
+This function lists the instances started via [`new/1`](#new-1), along with their
+respective settings as nested property lists.
+
+
+### info/1 ###
+
+
+
+info(P::pid()) -> [logger_info()]
+
+
+
+Lists the settings of a given logger instance.
+
+
+### new/1 ###
+
+
+
+new(Options::[{id, any()} | {input, list()} | {output, list()}]) -> {ok, pid()}
+
+
+
+
+Create a new logger instance.
+
+
+
+This function creates a logger process with the given input and output
+parameters.
+
+
+
+* `{id, ID}` is mainly for documentation and simplifying identification
+of instances returned by [`info/0`](#info-0).
+* `{input, PropList}` specifies what the logger listens to. Only the first
+`input` entry is regarded, but the option is mandatory.
+* `{output, PropList}` specifies what the logger should to with received
+data. Multiple `output` entries are allowed, and they will be processed
+in the order given.
+
+
+
+Valid input options:
+
+
+
+* `{mode, udp | tcp | internal | plugin}` defines the protocol
+* `{active, false | true | once | N}` provides flow control. Default: `true`.
+* (mode-specific options)
+
+
+
+Valid output options:
+
+
+
+* `{mode, tty | ets | plugin | internal}` defines output types
+* (output-specific options)
+
+
+
+Mode-specific options, `udp`:
+
+
+
+* `{port, integer()}` - UDP port number
+* `{options, list()}` - Options to pass to [`gen_udp:open/2`](gen_udp.md#open-2)
+
+
+
+Mode-specific options, `tcp`:
+
+
+
+* `{port, integer()}` - TCP port number
+* `{options, list()}` - Options to pass to [`gen_tcp:listen/2`](gen_tcp.md#listen-2)
+
+
+
+Mode-specific options, `tty`:
+
+
+
+* `{prefix, iolist()}` - Prefix string inserted before the data, which is
+printed as-is (note that any delimiter would need to be part of the prefix)
+
+
+
+Mode-specific options, `ets`:
+* `{table, ets:table()}` - Ets table identifier. If not specified, an
+ordered-set table will be created by the logger process. The incoming
+data will be inserted as `{erlang:now(), Data}`.
+
+
+
+Mode-specific options, `internal`:
+* `{process, PidOrRegname}` specifies another logger instance, which is to
+receive data from this logger (if used in output), or which is allowed
+to send to this logger (if used in input). If no process is given for
+input, any process can send data (on the form
+`{exometer_report_logger, Pid, Data}`) to this logger.
+
+
+
+Mode-specific options, `plugin`:
+
+
+* `{module, Module}` - name of callback module
+(behaviour: `exometer_report_logger`)
+* `{state, State}` - Passed as initial argument to
+`Module:logger_init_input/1` or `Module:logger_init_output/1`, depending
+on whether the plugin is specified as input or output.
+
+
+### start_link/1 ###
+
+
+
+start_link(Options::proplist()) -> {ok, pid()}
+
+
+
+
+Start function for logger instance.
+
+
+This function is the start function eventually called as a result from
+[`new/1`](#new-1), but whereas `new/1` creates a supervised instance, this
+function simply creates the process. It would normally not be used directly.
diff --git a/doc/exometer_util.md b/doc/exometer_util.md
index 293a5fe..65f3803 100644
--- a/doc/exometer_util.md
+++ b/doc/exometer_util.md
@@ -32,7 +32,7 @@ timestamp() = non_neg_integer()
+drop_duplicates/1
will drop all duplicate elements from a list of tuples identified by their first element.ensure_all_started/1 | |
get_datapoints/1 | |
get_env/2 | |
get_opt/2 | |
get_opt/3 | |
get_statistics/3 | Calculate statistics from a sorted list of values. |
get_statistics2/4 | |
get_status/1 | |
histogram/1 | |
histogram/2 | |
perc/2 | |
pick_items/2 | Pick values from specified positions in a sorted list of numbers. |
report_type/3 | |
set_call_count/2 | |
set_call_count/3 | |
set_event_flag/2 | |
set_status/2 | |
table/0 | |
tables/0 | |
test_event_flag/2 | |
timestamp/0 | Generate a millisecond-resolution timestamp. |
timestamp_to_datetime/1 | Convert timestamp to a regular datetime. |
@@ -60,6 +60,17 @@ drop_duplicates(List0::[tuple()]) -> [tuple()]
`drop_duplicates/1` will drop all duplicate elements from a list of tuples identified by their first element.
Elements which are not tuples will be dropped as well.
If called with a non-list argument, the argument is returned as is.
+
+
+### ensure_all_started/1 ###
+
+
+
+ensure_all_started(App::atom()) -> {ok, [atom()]} | {error, term()}
+
+
+
+
### get_datapoints/1 ###
@@ -74,6 +85,13 @@ If called with a non-list argument, the argument is returned as is.
`get_env(Key, Default) -> any()`
+
+
+### get_opt/2 ###
+
+`get_opt(K, Opts) -> any()`
+
+
### get_opt/3 ###
@@ -140,6 +158,13 @@ Fulpatchad med min/max av Magnus Feuer.
`histogram(Values, DataPoints) -> any()`
+
+
+### perc/2 ###
+
+`perc(P, Len) -> any()`
+
+
### pick_items/2 ###
diff --git a/doc/overview.edoc b/doc/overview.edoc
index 001e7e4..74b8322 100644
--- a/doc/overview.edoc
+++ b/doc/overview.edoc
@@ -423,6 +423,12 @@ Exometer Core defaults can be changed either through OTP application environment
variables or through the use of Basho's `cuttlefish'
([https://github.com/basho/cuttlefish]).
+Note: Exometer Core will check both the `exometer' and the `exometer_core'
+application environments. The `exometer' environment overrides the
+`exometer_core' environment. However, if only `exometer_core' is used, any
+`exometer' environment will simply be ignored. This is because of the
+application controller: environment data is not loaded until the application
+in question is loaded.
=== Configuring type - entry maps ===
diff --git a/rebar.config b/rebar.config
index fb94a4f..219614d 100644
--- a/rebar.config
+++ b/rebar.config
@@ -12,6 +12,7 @@
{lager, ".*", {git, "git://github.com/basho/lager.git", {tag,"2.0.3"}}},
{parse_trans, ".*", {git, "git://github.com/uwiger/parse_trans.git", {tag,"2.9"}}},
{folsom, "0.7.4p5", {git, "git://github.com/basho/folsom.git", {tag, "0.7.4p5"}}},
+ {meck, ".*", {git, "git://github.com/basho/meck.git", {tag,"0.8.2"}}},
{setup, ".*", {git, "git://github.com/uwiger/setup.git", {tag,"1.4"}}}
]}.
@@ -35,6 +36,8 @@
"https://github.com/Feuerlabs/exometer_core", "master"}}
]}.
+{ct_extra_params, "-config test/ct.config"}.
+
{xref_checks,
[
undefined_function_calls,
diff --git a/src/exo_montest.erl b/src/exo_montest.erl
index b70a466..0641c26 100644
--- a/src/exo_montest.erl
+++ b/src/exo_montest.erl
@@ -18,29 +18,29 @@
-export([copy_folsom/3]).
-export([behaviour/0,
- delete/3,
- get_datapoints/3,
- get_value/4,
- new/3,
- reset/3,
- sample/3,
- setopts/3,
- update/4]).
+ delete/3,
+ get_datapoints/3,
+ get_value/4,
+ new/3,
+ reset/3,
+ sample/3,
+ setopts/3,
+ update/4]).
behaviour() ->
entry.
copy_folsom(Name, Type, Opts) when is_tuple(Name) ->
{tuple_to_list(Name), ad_hoc, [{folsom_name, Name},
- {module, ?MODULE},
- {type, Type}
- | options(Type, Opts)]};
+ {module, ?MODULE},
+ {type, Type}
+ | options(Type, Opts)]};
copy_folsom(_, _, _) ->
false.
new(N, _, Opts) ->
{ok, {proplists:get_value(type, Opts, unknown),
- proplists:get_value(folsom_name, Opts, N)}}.
+ proplists:get_value(folsom_name, Opts, N)}}.
update(_, Value, counter, {_, Name}) ->
folsom_metrics:notify_existing_metric(Name, {inc,Value}, counter);
diff --git a/src/exometer.erl b/src/exometer.erl
index 114d857..8682c55 100644
--- a/src/exometer.erl
+++ b/src/exometer.erl
@@ -60,13 +60,15 @@
register_application/1
]).
+-export([global_status/1]).
+
-export([create_entry/1]). % called only from exometer_admin.erl
%% Convenience function for testing
-export([start/0, stop/0]).
-export_type([name/0, type/0, options/0, status/0, behaviour/0,
- entry/0, datapoint/0]).
+ entry/0, datapoint/0]).
-compile(inline).
@@ -90,8 +92,8 @@
%% @doc Start exometer and dependent apps (for testing).
start() ->
- lager:start(),
- application:start(exometer_core).
+ {ok,_} = exometer_util:ensure_all_started(exometer_core),
+ ok.
%% @doc Stop exometer and dependent apps (for testing).
stop() ->
@@ -201,40 +203,46 @@ ensure(Name, Type, Opts) when is_list(Name), is_list(Opts) ->
%% corresponding callback module will be called. For a disabled metric,
%% `ok' will be returned without any other action being taken.
%% @end
-update(Name, Value) when is_list(Name) ->
+update(Name, Value) ->
+ case exometer_global:status() of
+ enabled -> update_(Name, Value);
+ _ -> ok
+ end.
+
+update_(Name, Value) when is_list(Name) ->
case ets:lookup(Table = exometer_util:table(), Name) of
- [#exometer_entry{status = Status} = E]
- when ?IS_ENABLED(Status) ->
- case E of
- #exometer_entry{module = ?MODULE, type = counter} ->
+ [#exometer_entry{status = Status} = E]
+ when ?IS_ENABLED(Status) ->
+ case E of
+ #exometer_entry{module = ?MODULE, type = counter} ->
ets:update_counter(
Table, Name, {#exometer_entry.value, Value});
- #exometer_entry{module = ?MODULE,
- type = fast_counter, ref = {M, F}} ->
+ #exometer_entry{module = ?MODULE,
+ type = fast_counter, ref = {M, F}} ->
fast_incr(Value, M, F);
- #exometer_entry{module = ?MODULE, type = gauge} ->
- ets:update_element(
- ?EXOMETER_ENTRIES,
- Name, [{#exometer_entry.value, Value}]);
- #exometer_entry{behaviour = probe,
- type = T, ref = Pid} ->
+ #exometer_entry{module = ?MODULE, type = gauge} ->
+ ets:update_element(
+ ?EXOMETER_ENTRIES,
+ Name, [{#exometer_entry.value, Value}]);
+ #exometer_entry{behaviour = probe,
+ type = T, ref = Pid} ->
exometer_probe:update(Name, Value, T, Pid);
- #exometer_entry{module = M, behaviour = entry,
- type = Type, ref = Ref} ->
- M:update(Name, Value, Type, Ref)
- end,
- update_ok(Status, E, Value);
+ #exometer_entry{module = M, behaviour = entry,
+ type = Type, ref = Ref} ->
+ M:update(Name, Value, Type, Ref)
+ end,
+ update_ok(Status, E, Value);
[] ->
{error, not_found};
- _ ->
- ok
+ _ ->
+ ok
end.
update_ok(St, #exometer_entry{name = Name}, Value)
when St band 2#10 =:= 2#10 ->
try exometer_event ! {updated, Name, Value}
catch
- _:_ -> ok
+ _:_ -> ok
end;
update_ok(_, _, _) ->
ok.
@@ -251,24 +259,24 @@ update_ok(_, _, _) ->
%% @end
update_or_create(Name, Value) ->
case update(Name, Value) of
- {error, not_found} ->
- case exometer_admin:auto_create_entry(Name) of
- ok ->
- update(Name, Value);
- Error ->
- Error
- end;
- ok ->
- ok
+ {error, not_found} ->
+ case exometer_admin:auto_create_entry(Name) of
+ ok ->
+ update(Name, Value);
+ Error ->
+ Error
+ end;
+ ok ->
+ ok
end.
update_or_create(Name, Value, Type, Opts) ->
case update(Name, Value) of
- {error, not_found} ->
- ensure(Name, Type, Opts),
- update(Name, Value);
- ok ->
- ok
+ {error, not_found} ->
+ ensure(Name, Type, Opts),
+ update(Name, Value);
+ ok ->
+ ok
end.
fast_incr(N, M, F) when N > 0 ->
@@ -292,7 +300,7 @@ get_value(Name) when is_list(Name) ->
get_value(Name, default).
-spec get_value(name(), datapoint() | [datapoint()]) ->
- {ok, value()} | {error, not_found}.
+ {ok, value()} | {error, not_found}.
get_value(Name, DataPoint) when is_list(Name), is_integer(DataPoint) ->
get_value(Name, [DataPoint]);
@@ -317,21 +325,21 @@ get_value_(#exometer_entry{ status = Status }, _DataPoints)
%% If the value is cached, see if we can find it.
%% In the default case, call again with resolved data points.
get_value_(#exometer_entry{cache = Cache } = E,
- DataPoints) when Cache =/= 0 ->
+ DataPoints) when Cache =/= 0 ->
get_cached_value_(E, DataPoints);
get_value_(#exometer_entry{ module = ?MODULE,
- type = Type} = E, default)
+ type = Type} = E, default)
when Type == counter; Type == fast_counter; Type == gauge ->
get_value_(E, exometer_util:get_datapoints(E));
get_value_(#exometer_entry{ module = ?MODULE,
- type = counter} = E, DataPoints0) ->
+ type = counter} = E, DataPoints0) ->
DataPoints = datapoints(DataPoints0, E),
[get_ctr_datapoint(E, D) || D <- DataPoints];
get_value_(#exometer_entry{ module = ?MODULE,
- type = gauge, name = Name}, DataPoints0) ->
+ type = gauge, name = Name}, DataPoints0) ->
[E] = ets:lookup(?EXOMETER_ENTRIES, Name),
DataPoints = datapoints(DataPoints0, E),
[get_gauge_datapoint(E, D) || D <- DataPoints];
@@ -342,16 +350,16 @@ get_value_(#exometer_entry{module = ?MODULE,
[get_fctr_datapoint(E, D) || D <- DataPoints ];
get_value_(#exometer_entry{behaviour = entry,
- module = Mod,
- name = Name,
- type = Type,
- ref = Ref} = E, DataPoints0) ->
+ module = Mod,
+ name = Name,
+ type = Type,
+ ref = Ref} = E, DataPoints0) ->
Mod:get_value(Name, Type, Ref, datapoints(DataPoints0, E));
get_value_(#exometer_entry{behaviour = probe,
- name = Name,
- type = Type,
- ref = Ref} = E, DataPoints0) ->
+ name = Name,
+ type = Type,
+ ref = Ref} = E, DataPoints0) ->
exometer_probe:get_value(Name, Type, Ref, datapoints(DataPoints0, E)).
@@ -360,21 +368,21 @@ get_cached_value_(E, default) ->
get_cached_value_(E, exometer_util:get_datapoints(E));
get_cached_value_(#exometer_entry{name = Name,
- cache = CacheTTL } = E,
- DataPoints) ->
+ cache = CacheTTL } = E,
+ DataPoints) ->
%% Dig through all the data points and check for cache hit.
%% Store all cached KV pairs, and all keys that must be
%% read and cached.
{ Cached, Uncached } =
- lists:foldr(fun(DataPoint, {Cached1, Uncached1}) ->
- case exometer_cache:read(Name, DataPoint) of
- not_found ->
- { Cached1, [DataPoint | Uncached1] };
- {_, Value } ->
- { [{ DataPoint, Value } | Cached1], Uncached1 }
- end
- end, {[],[]}, DataPoints),
+ lists:foldr(fun(DataPoint, {Cached1, Uncached1}) ->
+ case exometer_cache:read(Name, DataPoint) of
+ not_found ->
+ { Cached1, [DataPoint | Uncached1] };
+ {_, Value } ->
+ { [{ DataPoint, Value } | Cached1], Uncached1 }
+ end
+ end, {[],[]}, DataPoints),
%% Go through all cache misses and retreive their actual values.
Result = get_value_(E#exometer_entry { cache = 0 }, Uncached),
@@ -402,20 +410,20 @@ delete(Name) when is_list(Name) ->
%% @end
sample(Name) when is_list(Name) ->
case ets:lookup(exometer_util:table(), Name) of
- [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) ->
- case E of
- #exometer_entry{module = ?MODULE, type = counter} -> ok;
- #exometer_entry{module = ?MODULE, type = fast_counter} -> ok;
- #exometer_entry{behaviour = probe,
- type = Type,
- ref = Ref} ->
- exometer_probe:sample(Name, Type, Ref),
- ok;
- #exometer_entry{behaviour = entry,
- module = M, type = Type, ref = Ref} ->
- M:sample(Name, Type, Ref)
- end;
- [_] -> disabled;
+ [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) ->
+ case E of
+ #exometer_entry{module = ?MODULE, type = counter} -> ok;
+ #exometer_entry{module = ?MODULE, type = fast_counter} -> ok;
+ #exometer_entry{behaviour = probe,
+ type = Type,
+ ref = Ref} ->
+ exometer_probe:sample(Name, Type, Ref),
+ ok;
+ #exometer_entry{behaviour = entry,
+ module = M, type = Type, ref = Ref} ->
+ M:sample(Name, Type, Ref)
+ end;
+ [_] -> disabled;
[] ->
{error, not_found}
end.
@@ -430,44 +438,50 @@ sample(Name) when is_list(Name) ->
%% the exometer entry, which can be recalled using {@link info/2}, and will
%% indicate the time that has passed since the metric was last reset.
%% @end
-reset(Name) when is_list(Name) ->
+reset(Name) ->
+ case exometer_global:status() of
+ enabled -> reset_(Name);
+ _ -> ok
+ end.
+
+reset_(Name) when is_list(Name) ->
case ets:lookup(exometer_util:table(), Name) of
- [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) ->
- case E of
- #exometer_entry{module = ?MODULE, type = counter} ->
- TS = exometer_util:timestamp(),
- [ets:update_element(T, Name, [{#exometer_entry.value, 0},
- {#exometer_entry.timestamp, TS}])
- || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
- ok;
- #exometer_entry{module = ?MODULE, type = fast_counter,
- ref = {M, F}} ->
- TS = exometer_util:timestamp(),
- set_call_count(M, F, true),
- [ets:update_element(T, Name, [{#exometer_entry.timestamp, TS}])
- || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
- ok;
- #exometer_entry{module = ?MODULE, type = gauge} ->
- TS = exometer_util:timestamp(),
- [ets:update_element(T, Name, [{#exometer_entry.value, 0},
- {#exometer_entry.timestamp, TS}])
- || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
- ok;
- #exometer_entry{behaviour = probe, type = Type, ref = Ref} ->
- [exometer_cache:delete(Name, DataPoint) ||
- DataPoint <- exometer_util:get_datapoints(E)],
- exometer_probe:reset(Name, Type, Ref),
- ok;
- #exometer_entry{behaviour = entry,
- module = M, type = Type, ref = Ref} ->
- [exometer_cache:delete(Name, DataPoint) ||
- DataPoint <- exometer_util:get_datapoints(E)],
- M:reset(Name, Type, Ref)
- end;
- [] ->
+ [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) ->
+ case E of
+ #exometer_entry{module = ?MODULE, type = counter} ->
+ TS = exometer_util:timestamp(),
+ [ets:update_element(T, Name, [{#exometer_entry.value, 0},
+ {#exometer_entry.timestamp, TS}])
+ || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
+ ok;
+ #exometer_entry{module = ?MODULE, type = fast_counter,
+ ref = {M, F}} ->
+ TS = exometer_util:timestamp(),
+ set_call_count(M, F, true),
+ [ets:update_element(T, Name, [{#exometer_entry.timestamp, TS}])
+ || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
+ ok;
+ #exometer_entry{module = ?MODULE, type = gauge} ->
+ TS = exometer_util:timestamp(),
+ [ets:update_element(T, Name, [{#exometer_entry.value, 0},
+ {#exometer_entry.timestamp, TS}])
+ || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
+ ok;
+ #exometer_entry{behaviour = probe, type = Type, ref = Ref} ->
+ [exometer_cache:delete(Name, DataPoint) ||
+ DataPoint <- exometer_util:get_datapoints(E)],
+ exometer_probe:reset(Name, Type, Ref),
+ ok;
+ #exometer_entry{behaviour = entry,
+ module = M, type = Type, ref = Ref} ->
+ [exometer_cache:delete(Name, DataPoint) ||
+ DataPoint <- exometer_util:get_datapoints(E)],
+ M:reset(Name, Type, Ref)
+ end;
+ [] ->
{error, not_found};
- _ ->
- ok
+ _ ->
+ ok
end.
@@ -490,29 +504,29 @@ setopts(Name, Options) when is_list(Name), is_list(Options) ->
[#exometer_entry{module = ?MODULE,
type = Type,
status = Status} = E] ->
- if ?IS_DISABLED(Status) ->
+ if ?IS_DISABLED(Status) ->
case lists:keyfind(status, 1, Options) of
{_, enabled} ->
- case Type of
- fast_counter -> setopts_fctr(E, Options);
- counter -> setopts_ctr(E, Options);
- gauge -> setopts_gauge(E, Options)
+ case Type of
+ fast_counter -> setopts_fctr(E, Options);
+ counter -> setopts_ctr(E, Options);
+ gauge -> setopts_gauge(E, Options)
end,
reporter_setopts(E, Options, enabled);
_ ->
{error, disabled}
end;
- ?IS_ENABLED(Status) ->
+ ?IS_ENABLED(Status) ->
case lists:keyfind(status, 1, Options) of
{_, disabled} ->
{_, Elems} = process_setopts(E, Options),
update_entry_elems(Name, Elems),
reporter_setopts(E, Options, disabled);
R when R==false; R=={status,disabled} ->
- case Type of
- fast_counter -> setopts_fctr(E, Options);
- counter -> setopts_ctr(E, Options);
- gauge -> setopts_gauge(E, Options)
+ case Type of
+ fast_counter -> setopts_fctr(E, Options);
+ counter -> setopts_ctr(E, Options);
+ gauge -> setopts_gauge(E, Options)
end,
reporter_setopts(E, Options, enabled)
end
@@ -536,12 +550,31 @@ setopts(Name, Options) when is_list(Name), is_list(Options) ->
{error, not_found}
end.
-module_setopts(#exometer_entry{behaviour = probe}=E, Options, NewStatus) ->
+module_setopts(#exometer_entry{behaviour = probe,
+ name = Name,
+ status = St0} = E, Options, NewStatus) ->
reporter_setopts(E, Options, NewStatus),
- exometer_probe:setopts(E, Options, NewStatus);
+ case NewStatus of
+ disabled when ?IS_ENABLED(St0) ->
+ exometer_cache:delete_name(Name),
+ exometer_probe:stop_probe(E),
+ update_entry_elems(Name, [{#exometer_entry.ref, undefined}]);
+ enabled when ?IS_DISABLED(St0) ->
+ %% Previously, probes were not stopped when disabled
+ OldRef = E#exometer_entry.ref,
+ case is_pid(OldRef) andalso is_process_alive(OldRef) of
+ true -> ok;
+ false ->
+ {ok, Ref} = exometer_probe:start_probe(E),
+ update_entry_elems(Name, [{#exometer_entry.ref, Ref}])
+ end;
+ _ ->
+ exometer_probe:setopts(E, Options, NewStatus)
+ end,
+ ok;
module_setopts(#exometer_entry{behaviour = entry,
- module=M} = E, Options, NewStatus) ->
+ module=M} = E, Options, NewStatus) ->
case [O || {K, _} = O <- Options,
not lists:member(K, [status, cache, ref])] of
[] ->
@@ -551,8 +584,8 @@ module_setopts(#exometer_entry{behaviour = entry,
ok ->
reporter_setopts(E, Options, NewStatus),
ok;
- E ->
- E
+ {error,_} = Error ->
+ Error
end
end.
@@ -626,25 +659,25 @@ info(#exometer_entry{} = E, Item) ->
info(Name, Item) ->
case ets:lookup(exometer_util:table(), Name) of
[#exometer_entry{} = E] ->
- info_(E, Item);
+ info_(E, Item);
_ ->
undefined
end.
info_(E, Item) ->
case Item of
- name -> E#exometer_entry.name;
- type -> E#exometer_entry.type;
- module -> E#exometer_entry.module;
- value -> get_value_(E,[]);
- cache -> E#exometer_entry.cache;
- status -> info_status(E#exometer_entry.status);
- timestamp -> E#exometer_entry.timestamp;
- options -> E#exometer_entry.options;
- ref -> E#exometer_entry.ref;
- entry -> E;
- datapoints-> exometer_util:get_datapoints(E);
- _ -> undefined
+ name -> E#exometer_entry.name;
+ type -> E#exometer_entry.type;
+ module -> E#exometer_entry.module;
+ value -> get_value_(E,[]);
+ cache -> E#exometer_entry.cache;
+ status -> info_status(E#exometer_entry.status);
+ timestamp -> E#exometer_entry.timestamp;
+ options -> E#exometer_entry.options;
+ ref -> E#exometer_entry.ref;
+ entry -> E;
+ datapoints-> exometer_util:get_datapoints(E);
+ _ -> undefined
end.
info_status(S) when ?IS_ENABLED(S) ->
@@ -666,12 +699,12 @@ datapoints(D, _) when is_list(D) ->
info(Name) ->
case ets:lookup(exometer_util:table(), Name) of
[#exometer_entry{} = E0] ->
- E = info_set_status(E0),
+ E = info_set_status(E0),
Flds = record_info(fields, exometer_entry),
lists:keyreplace(value, 1,
lists:zip(Flds, tl(tuple_to_list(E))) ++
[ {datapoints,
- exometer_util:get_datapoints(E)}],
+ exometer_util:get_datapoints(E)}],
{value, get_value_(E, [])});
_ ->
undefined
@@ -699,8 +732,8 @@ find_entries(Path) ->
[ { #exometer_entry{name = Pat, _ = '_'}, [],
[{{ {element, #exometer_entry.name, '$_'},
{element, #exometer_entry.type, '$_'},
- status_body_pattern()
- }}] } ]).
+ status_body_pattern()
+ }}] } ]).
get_values(Path) ->
Entries = find_entries(Path),
@@ -781,9 +814,9 @@ select_cont(Cont) ->
%% @end
aggregate(Pattern, DataPoints) ->
case aggr_select(Pattern) of
- [] -> [];
- Found ->
- aggregate(Found, DataPoints, orddict:new())
+ [] -> [];
+ Found ->
+ aggregate(Found, DataPoints, orddict:new())
end.
aggr_select(Pattern) ->
@@ -791,25 +824,47 @@ aggr_select(Pattern) ->
aggregate([N|Ns], DPs, Acc) when is_list(N) ->
case get_value(N, DPs) of
- {ok, Vals} ->
- aggregate(Ns, DPs, aggr_acc(Vals, Acc));
- _ ->
- aggregate(Ns, DPs, Acc)
+ {ok, Vals} ->
+ aggregate(Ns, DPs, aggr_acc(Vals, Acc));
+ _ ->
+ aggregate(Ns, DPs, Acc)
end;
aggregate([], _, Acc) ->
Acc.
aggr_acc([{D,V}|T], Acc) ->
if is_integer(V) ->
- aggr_acc(T, orddict:update(D, fun(Val) ->
- Val + V
- end, V, Acc));
+ aggr_acc(T, orddict:update(D, fun(Val) ->
+ Val + V
+ end, V, Acc));
true ->
- aggr_acc(T, Acc)
+ aggr_acc(T, Acc)
end;
aggr_acc([], Acc) ->
Acc.
+global_status(St) when St==enabled; St==disabled ->
+ Prev = exometer_global:status(),
+ if St =:= Prev -> ok;
+ true ->
+ parse_trans_mod:transform_module(
+ exometer_global, fun(Forms,_) -> pt(Forms, St) end, [])
+ end,
+ Prev.
+
+pt(Forms, St) ->
+ parse_trans:plain_transform(
+ fun(F) ->
+ plain_pt(F, St)
+ end, Forms).
+
+plain_pt({function, L, status, 0, [_]}, St) ->
+ {function, L, status, 0,
+ [{clause, L, [], [], [{atom, L, St}]}]};
+plain_pt(_, _) ->
+ continue.
+
+
%% Perform variable replacement in the ets select pattern.
%% We want to project the entries as a set of {Name, Type, Status} tuples.
%% This means we need to perform variable substitution in both guards and
@@ -864,27 +919,27 @@ g_subst(Ks) ->
%% Potentially save Name, Type and Status match head patterns
match_tail(N,T,S) ->
case is_dollar(N) of true ->
- [{N, {element,#exometer_entry.name,'$_'}}|match_tail(T,S)];
- false -> match_tail(T, S)
+ [{N, {element,#exometer_entry.name,'$_'}}|match_tail(T,S)];
+ false -> match_tail(T, S)
end.
match_tail(T,S) ->
case is_dollar(T) of true ->
- [{T, {element,#exometer_entry.type,'$_'}}|match_tail(S)];
- false -> match_tail(S)
+ [{T, {element,#exometer_entry.type,'$_'}}|match_tail(S)];
+ false -> match_tail(S)
end.
match_tail(S) ->
case is_dollar(S) of true ->
- [{S, status_element_pattern(S)}];
- false -> []
+ [{S, status_element_pattern(S)}];
+ false -> []
end.
is_dollar(A) when is_atom(A) ->
case atom_to_list(A) of
- [$$ | Rest] ->
- try _ = list_to_integer(Rest), true
- catch error:_ -> false
- end;
- _ -> false
+ [$$ | Rest] ->
+ try _ = list_to_integer(Rest), true
+ catch error:_ -> false
+ end;
+ _ -> false
end;
is_dollar(_) -> false.
@@ -892,8 +947,8 @@ is_dollar(_) -> false.
g_subst_({_,_}=X) -> X;
g_subst_(K) when is_atom(K) ->
{K, {{{element,#exometer_entry.name,'$_'},
- {element,#exometer_entry.type,'$_'},
- status_element_pattern({element,#exometer_entry.status,'$_'})}}}.
+ {element,#exometer_entry.type,'$_'},
+ status_element_pattern({element,#exometer_entry.status,'$_'})}}}.
%% The status attribute: bit 1 indicates enabled (1) or disabled (0)
@@ -904,7 +959,7 @@ status_element_pattern(S) ->
status_body_pattern() ->
{element,
{'+',{'band',
- {element,#exometer_entry.status,'$_'},1},1},
+ {element,#exometer_entry.status,'$_'},1},1},
{{disabled,enabled}}}.
p_subst(Ks) ->
@@ -913,7 +968,7 @@ p_subst_({_,_}=X) -> X;
p_subst_(K) when is_atom(K) ->
{K, {{{element,#exometer_entry.name,'$_'},
{element,#exometer_entry.type,'$_'},
- status_body_pattern()}}}.
+ status_body_pattern()}}}.
%% This function returns a list of elements for ets:update_element/3,
%% to be used for updating the #exometer_entry{} instances.
@@ -937,48 +992,48 @@ process_setopts_(#exometer_entry{options = OldOpts} = Entry, Options) ->
fun
({cache, Val},
{#exometer_entry{cache = Cache0} = E, Elems} = Acc) ->
- if is_integer(Val), Val >= 0 ->
- if Val =/= Cache0 ->
- {E#exometer_entry{cache = Val},
- add_elem(cache, Val, Elems)};
- true ->
- Acc
- end;
- true -> error({illegal, {cache, Val}})
- end;
+ if is_integer(Val), Val >= 0 ->
+ if Val =/= Cache0 ->
+ {E#exometer_entry{cache = Val},
+ add_elem(cache, Val, Elems)};
+ true ->
+ Acc
+ end;
+ true -> error({illegal, {cache, Val}})
+ end;
({status, Status}, {#exometer_entry{status = Status0} = E, Elems} = Acc) ->
- case is_status_change(Status, Status0) of
- {true, Status1} ->
- {E#exometer_entry{status = Status1},
- add_elem(status, Status1, Elems)};
- false ->
- Acc
- end;
- ({update_event, UE},
- {#exometer_entry{status = Status0} = E, Elems} = Acc)
- when is_boolean(UE) ->
- case (exometer_util:test_event_flag(update, Status0) == UE) of
- true -> Acc;
- false ->
- %% value changed
- if UE ->
- Status = exometer_util:set_event_flag(
- update, E#exometer_entry.status),
- {E#exometer_entry{status = Status},
- add_elem(status, Status, Elems)};
- true ->
- Status = exometer_util:clear_event_flag(
- update, E#exometer_entry.status),
- {E#exometer_entry{status = Status},
- add_elem(status, Status, Elems)}
- end
- end;
- ({ref,R}, {E, Elems}) ->
- {E#exometer_entry{ref = R}, add_elem(ref, R, Elems)};
+ case is_status_change(Status, Status0) of
+ {true, Status1} ->
+ {E#exometer_entry{status = Status1},
+ add_elem(status, Status1, Elems)};
+ false ->
+ Acc
+ end;
+ ({update_event, UE},
+ {#exometer_entry{status = Status0} = E, Elems} = Acc)
+ when is_boolean(UE) ->
+ case (exometer_util:test_event_flag(update, Status0) == UE) of
+ true -> Acc;
+ false ->
+ %% value changed
+ if UE ->
+ Status = exometer_util:set_event_flag(
+ update, E#exometer_entry.status),
+ {E#exometer_entry{status = Status},
+ add_elem(status, Status, Elems)};
+ true ->
+ Status = exometer_util:clear_event_flag(
+ update, E#exometer_entry.status),
+ {E#exometer_entry{status = Status},
+ add_elem(status, Status, Elems)}
+ end
+ end;
+ ({ref,R}, {E, Elems}) ->
+ {E#exometer_entry{ref = R}, add_elem(ref, R, Elems)};
({_,_}, Acc) ->
- Acc
- end, {Entry, []}, Options),
- {E1, [{#exometer_entry.options, update_opts(Options, OldOpts)}|Elems]}.
+ Acc
+ end, {Entry, []}, Options),
+ {E1, [{#exometer_entry.options, update_opts(Options, OldOpts)}|Elems]}.
is_status_change(enabled, St) ->
if ?IS_ENABLED(St) -> false;
@@ -1020,13 +1075,22 @@ type_arg_first(Opts) ->
%% Retrieve individual data points for the counter maintained by
%% the exometer record itself.
get_ctr_datapoint(#exometer_entry{name = Name}, value) ->
- {value, lists:sum([ets:lookup_element(T, Name, #exometer_entry.value)
- || T <- exometer_util:tables()])};
+ {value, counter_sum(Name)};
+get_ctr_datapoint(#exometer_entry{name = Name}, value16) ->
+ {value16, counter_sum(Name) rem 16#FFFF};
+get_ctr_datapoint(#exometer_entry{name = Name}, value32) ->
+ {value32, counter_sum(Name) rem 16#FFFFFFFF};
+get_ctr_datapoint(#exometer_entry{name = Name}, value64) ->
+ {value64, counter_sum(Name) rem 16#FFFFFFFFFFFFFFFF};
get_ctr_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) ->
{ms_since_reset, exometer_util:timestamp() - TS};
get_ctr_datapoint(#exometer_entry{}, Undefined) ->
{Undefined, undefined}.
+counter_sum(Name) ->
+ lists:sum([ets:lookup_element(T, Name, #exometer_entry.value)
+ || T <- exometer_util:tables()]).
+
get_gauge_datapoint(#exometer_entry{value = Value}, value) ->
{value, Value};
get_gauge_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) ->
@@ -1054,7 +1118,7 @@ get_fctr_datapoint(#exometer_entry{ }, Undefined) ->
create_entry(#exometer_entry{module = exometer,
type = Type} = E) when Type == counter;
- Type == gauge ->
+ Type == gauge ->
E1 = E#exometer_entry{value = 0, timestamp = exometer_util:timestamp()},
insert_aliases(E1),
[ets:insert(T, E1) || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
@@ -1084,21 +1148,28 @@ create_entry(#exometer_entry{module = exometer,
create_entry(#exometer_entry{module = Module,
type = Type,
name = Name,
- options = Opts} = E) ->
+ status = Status,
+ options = Opts} = E) ->
case
- case Module:behaviour() of
- probe ->
- {probe, exometer_probe:new(Name, Type, [{ arg, Module} | Opts ]) };
- entry ->
- {entry, Module:new(Name, Type, Opts) };
-
- Other -> Other
- end
+ case Module:behaviour() of
+ probe ->
+ if ?IS_ENABLED(Status) ->
+ {probe, exometer_probe:new(
+ Name, Type, [{ arg, Module} | Opts ])};
+ ?IS_DISABLED(Status) ->
+ %% Don't start probe if disabled
+ {probe, ok}
+ end;
+ entry ->
+ {entry, Module:new(Name, Type, Opts) };
+
+ Other -> Other
+ end
of
{Behaviour, ok }->
insert_aliases(E),
[ets:insert(T, E#exometer_entry { behaviour = Behaviour })
- || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
+ || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]],
ok;
{Behaviour, {ok, Ref}} ->
insert_aliases(E),
@@ -1140,10 +1211,10 @@ set_call_count(M, F, Bool) when is_atom(M), is_atom(F), is_boolean(Bool) ->
%%
register_application() ->
case application:get_application() of
- {ok, App} ->
- register_application(App);
- Other ->
- {error, Other}
+ {ok, App} ->
+ register_application(App);
+ Other ->
+ {error, Other}
end.
-spec register_application(_Application::atom()) -> ok | error().
diff --git a/src/exometer_admin.erl b/src/exometer_admin.erl
index cbe361e..c4d0a10 100644
--- a/src/exometer_admin.erl
+++ b/src/exometer_admin.erl
@@ -45,8 +45,9 @@
]).
-export([find_auto_template/1, prefixes/1, make_patterns/2]).
--export([monitor/2, demonitor/1]).
+-export([monitor/2, monitor/3, demonitor/1]).
+-compile({no_auto_import, [monitor/3]}).
-include("exometer.hrl").
-include("log.hrl").
@@ -99,20 +100,20 @@ load_predefined() ->
register_application(App) ->
%% Ignore if exometer is not running
case whereis(exometer_admin) of
- undefined -> ok;
- _ ->
- case setup:get_env(App, exometer_defaults) of
- {ok, E} ->
- do_load_defaults(App, get_predef(E));
- undefined ->
- ok
- end,
- case setup:get_env(App, exometer_predefined) of
- {ok, P} ->
- do_load_predef(App, get_predef(P));
- undefined ->
- ok
- end
+ undefined -> ok;
+ _ ->
+ case setup:get_env(App, exometer_defaults) of
+ {ok, E} ->
+ do_load_defaults(App, get_predef(E));
+ undefined ->
+ ok
+ end,
+ case setup:get_env(App, exometer_predefined) of
+ {ok, P} ->
+ do_load_predef(App, get_predef(P));
+ undefined ->
+ ok
+ end
end.
get_predef({script, F} ) -> ok(file:script(F, []), F);
@@ -124,36 +125,41 @@ do_load_defaults(Src, L) when is_list(L) ->
lists:foreach(
fun({NamePattern, Type, Spec}) ->
try set_default(NamePattern, Type, Spec)
- catch
- error:E ->
- lager:error("Defaults(~p): ERROR: ~p~n", [Src, E])
- end
+ catch
+ error:E ->
+ lager:error("Defaults(~p): ERROR: ~p~n", [Src, E])
+ end
end, L).
do_load_predef(Src, L) when is_list(L) ->
lists:foreach(
fun({Name, Type, Options}) ->
new_entry(Name, Type, Options);
- ({delete, Key}) ->
- predef_delete_entry(Key, Src);
- ({re_register, {Name, Type, Options}}) ->
- re_register_entry(Name, Type, Options);
- ({select_delete, Pat}) ->
- Found = exometer:select(Pat),
- lists:foreach(
- fun({K,_,_}) ->
- predef_delete_entry(K, Src);
- (Other) ->
- lager:error("Predef(~p): ~p~n",
- [Src, {bad_pattern,Other}])
- end, Found)
+ ({delete, Key}) ->
+ predef_delete_entry(Key, Src);
+ ({re_register, {Name, Type, Options}}) ->
+ re_register_entry(Name, Type, Options);
+ ({select_delete, Pat}) ->
+ Found = exometer:select(Pat),
+ lists:foreach(
+ fun({K,_,_}) ->
+ predef_delete_entry(K, Src);
+ (Other) ->
+ lager:error("Predef(~p): ~p~n",
+ [Src, {bad_pattern,Other}])
+ end, Found);
+ ({aliases, Aliases}) ->
+ lists:foreach(
+ fun({Alias, Entry, DP}) ->
+ exometer_alias:new(Alias, Entry, DP)
+ end, Aliases)
end, L).
predef_delete_entry(Key, Src) ->
case delete_entry(Key) of
- ok -> ok;
- Error ->
- lager:error("Predef(~p): ~p~n", [Src, Error])
+ ok -> ok;
+ Error ->
+ lager:error("Predef(~p): ~p~n", [Src, Error])
end.
ok({ok, Res}, _) -> Res;
@@ -189,10 +195,10 @@ re_register_entry(Name, Type, Opts) ->
repair_entry(Name) ->
case gen_server:call(?MODULE, {repair_entry, Name}) of
- {error, Reason} ->
- error(Reason);
- ok ->
- ok
+ {error, Reason} ->
+ error(Reason);
+ ok ->
+ ok
end.
delete_entry(Name) ->
@@ -201,10 +207,10 @@ delete_entry(Name) ->
ensure(Name, Type, Opts) ->
{Type1, Opt1} = check_type_arg(Type, Opts),
case gen_server:call(?MODULE, {ensure, Name, Type1, Opt1}) of
- {error, Reason} ->
- error(Reason);
- ok ->
- ok
+ {error, Reason} ->
+ error(Reason);
+ ok ->
+ ok
end.
auto_create_entry(Name) ->
@@ -223,16 +229,19 @@ check_type_arg(Type, Opts) ->
{Type, Opts}.
monitor(Name, Pid) when is_pid(Pid) ->
- gen_server:cast(?MODULE, {monitor, Name, Pid}).
+ monitor(Name, Pid, delete).
+
+monitor(Name, Pid, OnError) when is_pid(Pid) ->
+ gen_server:cast(?MODULE, {monitor, Name, Pid, OnError}).
demonitor(Pid) when is_pid(Pid) ->
gen_server:cast(?MODULE, {demonitor, Pid}).
opts_to_rec(Type, Opts0) ->
Opts = case lists:keymember(module, 1, Opts0) of
- true -> Opts0;
- false -> [{module, module(Type)}|Opts0]
- end,
+ true -> Opts0;
+ false -> [{module, module(Type)}|Opts0]
+ end,
Flds = record_info(fields, exometer_entry),
lists:foldr(fun({K,V}, Acc) ->
setelement(pos(K, Flds), Acc, V)
@@ -256,7 +265,7 @@ start_link() ->
init(_) ->
{ok, #st{}}.
-handle_call({new_entry, Name, Type, Opts, AllowExisting}, _From, S) ->
+handle_call({new_entry, Name, Type, Opts, AllowExisting} = _Req, _From, S) ->
try
#exometer_entry{options = NewOpts} = E0 =
lookup_definition(Name, Type, Opts),
@@ -264,68 +273,78 @@ handle_call({new_entry, Name, Type, Opts, AllowExisting}, _From, S) ->
case {ets:member(exometer_util:table(), Name), AllowExisting} of
{true, false} ->
{reply, {error, exists}, S};
- _ ->
+ _Other ->
+ lager:debug("_Other = ~p~n", [_Other]),
E1 = process_opts(E0, NewOpts),
- Res = exometer:create_entry(E1),
- exometer_report:new_entry(E1),
+ Res = try exometer:create_entry(E1),
+ exometer_report:new_entry(E1)
+ catch
+ error:Error1 ->
+ lager:debug(
+ "ERROR create_entry(~p) :- ~p~n~p",
+ [E1, Error1, erlang:get_stacktrace()]),
+ erlang:error(Error1)
+ end,
{reply, Res, S}
end
catch
error:Error ->
+ lager:error("~p -*-> error:~p~n~p~n",
+ [_Req, Error, erlang:get_stacktrace()]),
{reply, {error, Error}, S}
end;
handle_call({repair_entry, Name}, _From, S) ->
try
- #exometer_entry{} = E = exometer:info(Name, entry),
- delete_entry_(Name),
- exometer:create_entry(E),
- {reply, ok, S}
+ #exometer_entry{} = E = exometer:info(Name, entry),
+ delete_entry_(Name),
+ exometer:create_entry(E),
+ {reply, ok, S}
catch
- error:Error ->
- {reply, {error, Error}, S}
+ error:Error ->
+ {reply, {error, Error}, S}
end;
handle_call({propose, Name, Type, Opts}, _From, S) ->
try
- #exometer_entry{options = NewOpts} = E0 =
- lookup_definition(Name, Type, Opts),
- E1 = process_opts(E0, NewOpts),
- {reply, exometer_info:pp(E1), S}
+ #exometer_entry{options = NewOpts} = E0 =
+ lookup_definition(Name, Type, Opts),
+ E1 = process_opts(E0, NewOpts),
+ {reply, exometer_info:pp(E1), S}
catch
- error:Error ->
- {reply, {error, Error}, S}
+ error:Error ->
+ {reply, {error, Error}, S}
end;
handle_call({delete_entry, Name}, _From, S) ->
{reply, delete_entry_(Name), S};
handle_call({ensure, Name, Type, Opts}, _From, S) ->
case ets:lookup(exometer_util:table(), Name) of
- [#exometer_entry{type = Type}] ->
- {reply, ok, S};
- [#exometer_entry{type = _OtherType}] ->
- {reply, {error, type_mismatch}, S};
- [] ->
- #exometer_entry{options = OptsTemplate} = E0 =
- lookup_definition(Name, Type, Opts),
- E1 = process_opts(E0, OptsTemplate ++ Opts),
- Res = exometer:create_entry(E1),
- report_new_entry(E1),
- {reply, Res, S}
+ [#exometer_entry{type = Type}] ->
+ {reply, ok, S};
+ [#exometer_entry{type = _OtherType}] ->
+ {reply, {error, type_mismatch}, S};
+ [] ->
+ #exometer_entry{options = OptsTemplate} = E0 =
+ lookup_definition(Name, Type, Opts),
+ E1 = process_opts(E0, OptsTemplate ++ Opts),
+ Res = exometer:create_entry(E1),
+ report_new_entry(E1),
+ {reply, Res, S}
end;
handle_call({auto_create, Name}, _From, S) ->
case find_auto_template(Name) of
- false ->
- {reply, {error, no_template}, S};
- #exometer_entry{options = Opts} = E ->
- E1 = process_opts(E#exometer_entry{name = Name}, Opts),
- Res = exometer:create_entry(E1),
- report_new_entry(E1),
- {reply, Res, S}
+ false ->
+ {reply, {error, no_template}, S};
+ #exometer_entry{options = Opts} = E ->
+ E1 = process_opts(E#exometer_entry{name = Name}, Opts),
+ Res = exometer:create_entry(E1),
+ report_new_entry(E1),
+ {reply, Res, S}
end;
handle_call(_, _, S) ->
{reply, error, S}.
-handle_cast({monitor, Name, Pid}, S) ->
+handle_cast({monitor, Name, Pid, OnError}, S) ->
Ref = erlang:monitor(process, Pid),
- put(Ref, Name),
+ put(Ref, {Name, OnError}),
put(Pid, Ref),
{noreply, S};
handle_cast({demonitor, Pid}, S) ->
@@ -345,14 +364,21 @@ handle_info({'DOWN', Ref, _, Pid, _}, S) ->
case get(Ref) of
undefined ->
{noreply, S};
- Proc when is_atom(Proc) ->
+ {Name, OnError} when is_atom(Name); is_list(Name) ->
+ erase(Ref),
+ erase(Pid),
+ on_error(Name, OnError),
+ {noreply, S};
+ Name when is_atom(Name) ->
+ %% BW compat
erase(Ref),
erase(Pid),
{noreply, S};
Name when is_list(Name) ->
+ %% BW compat
erase(Ref),
erase(Pid),
- try delete_entry_(Name) catch error:_ -> ok end,
+ on_error(Name, delete),
{noreply, S}
end;
handle_info(_, S) ->
@@ -363,17 +389,17 @@ terminate(_, _) ->
code_change(_, S, _) ->
case ets:info(?EXOMETER_REPORTERS, name) of
- undefined -> create_reporter_tabs();
- _ -> ok
+ undefined -> create_reporter_tabs();
+ _ -> ok
end,
{ok, S}.
create_reporter_tabs() ->
Heir = {heir, whereis(exometer_sup), []},
ets:new(?EXOMETER_REPORTERS, [public, named_table, set,
- {keypos, 2}, Heir]),
+ {keypos, 2}, Heir]),
ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set,
- {keypos, 2}, Heir]).
+ {keypos, 2}, Heir]).
create_ets_tabs() ->
@@ -385,10 +411,10 @@ create_ets_tabs() ->
{keypos, 2}]),
ets:new(?EXOMETER_ENTRIES, [public, named_table, ordered_set,
{keypos, 2}]),
- ets:new(?EXOMETER_REPORTERS, [public, named_table, set,
- {keypos, 2}]),
- ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set,
- {keypos, 2}]);
+ ets:new(?EXOMETER_REPORTERS, [public, named_table, set,
+ {keypos, 2}]),
+ ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set,
+ {keypos, 2}]);
_ ->
true
end.
@@ -399,7 +425,47 @@ tables() ->
%% ====
-lookup_definition(Name, ad_hoc, Opts) ->
+on_error(Name, {restart, {M, F, A}}) ->
+ try call_restart(M, F, A) of
+ {ok, Ref} ->
+ if is_list(Name) ->
+ [ets:update_element(T, Name, [{#exometer_entry.ref, Ref}])
+ || T <- exometer_util:tables()];
+ true -> ok
+ end,
+ ok;
+ disable ->
+ try_disable_entry_(Name);
+ delete ->
+ try_delete_entry_(Name);
+ Other ->
+ restart_failed(Name, Other)
+ catch error:R ->
+ restart_failed(Name, {error, R})
+ end,
+ ok;
+on_error(Name, delete) ->
+ try_delete_entry_(Name);
+on_error(_Proc, _OnError) ->
+ %% Not good, but will do for now.
+ lager:debug("Unrecognized OnError: ~p (~p)~n", [_OnError, _Proc]),
+ ok.
+
+call_restart(M, F, A) ->
+ apply(M, F, A).
+
+restart_failed(Name, Error) ->
+ lager:debug("Restart failed ~p: ~p~n", [Name, Error]),
+ if is_list(Name) ->
+ try_delete_entry_(Name);
+ true ->
+ ok
+ end.
+
+lookup_definition(Name, Type, Opts) ->
+ check_aliases(lookup_definition_(Name, Type, Opts)).
+
+lookup_definition_(Name, ad_hoc, Opts) ->
case [K || K <- [module, type], not lists:keymember(K, 1, Opts)] of
[] ->
{E0, Opts1} =
@@ -421,40 +487,49 @@ lookup_definition(Name, ad_hoc, Opts) ->
[_|_] = Missing ->
error({required, Missing})
end;
-lookup_definition(Name, Type, Opts) ->
+lookup_definition_(Name, Type, Opts) ->
E = case ets:prev(?EXOMETER_SHARED, {default, Type, <<>>}) of
- {default, Type, N} = D0 when N==[''], N==Name ->
- case ets:lookup(?EXOMETER_SHARED, D0) of
- [#exometer_entry{} = Def] ->
- Def;
- [] ->
- default_definition_(Name, Type)
- end;
- {default, OtherType, _} when Type=/=OtherType ->
- exometer_default(Name, Type);
- '$end_of_table' ->
- exometer_default(Name, Type);
- _ ->
- default_definition_(Name, Type)
- end,
+ {default, Type, N} = D0 when N==[''], N==Name ->
+ case ets:lookup(?EXOMETER_SHARED, D0) of
+ [#exometer_entry{} = Def] ->
+ Def;
+ [] ->
+ default_definition_(Name, Type)
+ end;
+ {default, OtherType, _} when Type=/=OtherType ->
+ exometer_default(Name, Type);
+ '$end_of_table' ->
+ exometer_default(Name, Type);
+ _ ->
+ default_definition_(Name, Type)
+ end,
merge_opts(Opts, E).
merge_opts(Opts, #exometer_entry{options = DefOpts} = E) ->
Opts1 = lists:foldl(fun({'--', Keys}, Acc) ->
- case is_list(Keys) of
- true ->
- lists:foldl(
- fun(K,Acc1) ->
- lists:keydelete(K, 1, Acc1)
- end, Acc, Keys);
- false ->
- error({invalid_option,'--'})
- end;
- ({K,V}, Acc) ->
- lists:keystore(K, 1, Acc, {K,V})
- end, DefOpts, Opts),
+ case is_list(Keys) of
+ true ->
+ lists:foldl(
+ fun(K,Acc1) ->
+ lists:keydelete(K, 1, Acc1)
+ end, Acc, Keys);
+ false ->
+ error({invalid_option,'--'})
+ end;
+ ({K,V}, Acc) ->
+ lists:keystore(K, 1, Acc, {K,V})
+ end, DefOpts, Opts),
E#exometer_entry{options = Opts1}.
+check_aliases(#exometer_entry{name = N, options = Opts} = E) ->
+ case lists:keyfind(aliases, 1, Opts) of
+ {_, Aliases} ->
+ exometer_alias:check_map([{N, Aliases}]);
+ _ ->
+ ok
+ end,
+ E.
+
default_definition_(Name, Type) ->
case search_default(Name, Type) of
#exometer_entry{} = E ->
@@ -494,11 +569,11 @@ search_default(Name, Type) ->
sort_defaults(L) ->
lists:sort(fun comp_templates/2,
- [E || #exometer_entry{type = T} = E <- L,
- T =/= function andalso T =/= cpu]).
+ [E || #exometer_entry{type = T} = E <- L,
+ T =/= function andalso T =/= cpu]).
comp_templates(#exometer_entry{name = {default, _, A}, type = Ta},
- #exometer_entry{name = {default, _, B}, type = Tb}) ->
+ #exometer_entry{name = {default, _, B}, type = Tb}) ->
comp_names(A, B, Ta, Tb).
@@ -521,10 +596,10 @@ comp_types(A, B) -> A =< B.
%% @end
find_auto_template(Name) ->
case sort_defaults(ets:select(?EXOMETER_SHARED,
- make_patterns('_', Name))) of
- [] -> false;
- [#exometer_entry{name = {default,_,['']}}|_] -> false;
- [#exometer_entry{} = E|_] -> E
+ make_patterns('_', Name))) of
+ [] -> false;
+ [#exometer_entry{name = {default,_,['']}}|_] -> false;
+ [#exometer_entry{} = E|_] -> E
end.
make_patterns(Type, Name) when is_list(Name) ->
@@ -568,34 +643,53 @@ process_opts(Entry, Options) ->
%% Unknown option, pass on to exometer entry options list, replacing
%% any earlier versions of the same option.
({cache, Val}, E) ->
- if is_integer(Val), Val >= 0 ->
- E#exometer_entry{cache = Val};
- true ->
- error({illegal, {cache, Val}})
- end;
+ if is_integer(Val), Val >= 0 ->
+ E#exometer_entry{cache = Val};
+ true ->
+ error({illegal, {cache, Val}})
+ end;
({status, Status}, #exometer_entry{} = E) ->
- if Status==enabled; Status==disabled ->
- Status1 = exometer_util:set_status(
- Status, Entry#exometer_entry.status),
- E#exometer_entry{status = Status1};
- true ->
- error({illegal, {status, Status}})
- end;
- ({update_event, UE}, #exometer_entry{} = E) when is_boolean(UE) ->
- if UE ->
- Status = exometer_util:set_event_flag(
- update, E#exometer_entry.status),
- E#exometer_entry{status = Status};
- true ->
- Status = exometer_util:clear_event_flag(
- update, E#exometer_entry.status),
- E#exometer_entry{status = Status}
- end;
+ if Status==enabled; Status==disabled ->
+ Status1 = exometer_util:set_status(
+ Status, Entry#exometer_entry.status),
+ E#exometer_entry{status = Status1};
+ true ->
+ error({illegal, {status, Status}})
+ end;
+ ({update_event, UE}, #exometer_entry{} = E) when is_boolean(UE) ->
+ if UE ->
+ Status = exometer_util:set_event_flag(
+ update, E#exometer_entry.status),
+ E#exometer_entry{status = Status};
+ true ->
+ Status = exometer_util:clear_event_flag(
+ update, E#exometer_entry.status),
+ E#exometer_entry{status = Status}
+ end;
({_Opt, _Val}, #exometer_entry{} = Entry1) ->
- Entry1
- end, Entry#exometer_entry{options = Options}, Options).
+ Entry1
+ end, Entry#exometer_entry{options = Options}, Options).
+
+try_disable_entry_(Name) when is_list(Name) ->
+ try exometer:setopts(Name, [{status, disabled}])
+ catch
+ error:Err ->
+ lager:debug("Couldn't disable ~p: ~p~n", [Name, Err]),
+ try_delete_entry_(Name)
+ end;
+try_disable_entry_(_Name) ->
+ ok.
+
+try_delete_entry_(Name) ->
+ try delete_entry_(Name)
+ catch
+ error:R ->
+ lager:debug("Couldn't delete ~p: ~p~n", [Name, R]),
+ ok
+ end.
delete_entry_(Name) ->
+ exometer_cache:delete_name(Name),
case ets:lookup(exometer_util:table(), Name) of
[#exometer_entry{module = exometer, type = Type}] when Type==counter;
Type==gauge ->
@@ -604,34 +698,30 @@ delete_entry_(Name) ->
ok;
[#exometer_entry{module = exometer, type = fast_counter,
ref = {M, F}}] ->
- try
- exometer_util:set_call_count(M, F, false)
- after
- [ets:delete(T, Name) ||
- T <- [?EXOMETER_ENTRIES|exometer_util:tables()]]
- end,
+ try
+ exometer_util:set_call_count(M, F, false)
+ after
+ [ets:delete(T, Name) ||
+ T <- [?EXOMETER_ENTRIES|exometer_util:tables()]]
+ end,
ok;
[#exometer_entry{behaviour = probe,
- type = Type, ref = Ref} = E] ->
- [ exometer_cache:delete(Name, DataPoint) ||
- DataPoint <- exometer_util:get_datapoints(E)],
- try
- exometer_probe:delete(Name, Type, Ref)
- after
- [ets:delete(T, Name) ||
- T <- [?EXOMETER_ENTRIES|exometer_util:tables()]]
- end,
- ok;
+ type = Type, ref = Ref}] ->
+ try
+ exometer_probe:delete(Name, Type, Ref)
+ after
+ [ets:delete(T, Name) ||
+ T <- [?EXOMETER_ENTRIES|exometer_util:tables()]]
+ end,
+ ok;
[#exometer_entry{module= Mod, behaviour = entry,
- type = Type, ref = Ref} = E] ->
- [ exometer_cache:delete(Name, DataPoint) ||
- DataPoint <- exometer_util:get_datapoints(E)],
+ type = Type, ref = Ref}] ->
try Mod:delete(Name, Type, Ref)
after
[ets:delete(T, Name) ||
T <- [?EXOMETER_ENTRIES|exometer_util:tables()]]
end,
- ok;
+ ok;
[] ->
{error, not_found}
end.
diff --git a/src/exometer_alias.erl b/src/exometer_alias.erl
index c7178dd..bb15071 100644
--- a/src/exometer_alias.erl
+++ b/src/exometer_alias.erl
@@ -23,26 +23,27 @@
-behaviour(gen_server).
-export([new/3,
- load/1,
- unload/1,
- delete/1,
- update/2,
- resolve/1,
+ load/1,
+ unload/1,
+ check_map/1,
+ delete/1,
+ update/2,
+ resolve/1,
reverse_map/2,
- get_value/1,
- prefix_match/1,
- prefix_foldl/3,
- prefix_foldr/3,
- regexp_foldl/3,
- regexp_foldr/3]).
+ get_value/1,
+ prefix_match/1,
+ prefix_foldl/3,
+ prefix_foldr/3,
+ regexp_foldl/3,
+ regexp_foldr/3]).
-export([start_link/0,
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3]).
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
-define(TAB, ?MODULE).
-define(COMPILED_RE(P), is_tuple(P), element(1, P) == re_pattern).
@@ -75,11 +76,13 @@
%% type, and returns `{error, exists}' if the alias has already been
%% registered.
%% @end
-new(Alias, Entry, DP) when is_list(Entry), is_atom(DP), is_atom(Alias);
- is_list(Entry), is_atom(DP), is_binary(Alias);
- is_list(Entry), is_integer(DP), is_atom(Alias);
- is_list(Entry), is_integer(DP), is_binary(Alias) ->
- gen_server:call(?MODULE, {new, Alias, Entry, DP}).
+new(Alias, Entry, DP) ->
+ case valid_arg(Alias, Entry, DP) of
+ true ->
+ gen_server:call(?MODULE, {new, Alias, Entry, DP});
+ false ->
+ {error, invalid}
+ end.
-spec load(fun(() -> stat_map())) -> ok.
%% @doc Load a list of mappings between entry+datapoint pairs and aliases.
@@ -120,10 +123,10 @@ delete(Alias) ->
resolve(Alias) ->
Key = to_key(Alias),
case ets_lookup(Key) of
- [#alias{entry = Entry, dp = DP}] ->
- {Entry, DP};
- [] ->
- error
+ [#alias{entry = Entry, dp = DP}] ->
+ {Entry, DP};
+ [] ->
+ error
end.
-spec reverse_map(name() | '_', dp() | '_') -> [{alias(),name(),dp()}].
@@ -147,15 +150,15 @@ reverse_map(Name, Datapoint) ->
%% @end
get_value(Alias) ->
case resolve(Alias) of
- {Entry, DP} ->
- case exometer:get_value(Entry, [DP]) of
- {ok, [{_, Value}]} ->
- {ok, Value};
- Error ->
- Error
- end;
- error ->
- {error, not_found}
+ {Entry, DP} ->
+ case exometer:get_value(Entry, [DP]) of
+ {ok, [{_, Value}]} ->
+ {ok, Value};
+ Error ->
+ Error
+ end;
+ error ->
+ {error, not_found}
end.
-spec update(alias(), any()) -> ok | {error, any()}.
@@ -167,10 +170,10 @@ get_value(Alias) ->
%% @end
update(Alias, Value) ->
case resolve(Alias) of
- {Entry, _} ->
- exometer:update(Entry, Value);
- error ->
- {error, not_found}
+ {Entry, _} ->
+ exometer:update(Entry, Value);
+ error ->
+ {error, not_found}
end.
-spec prefix_match(binary()) -> [{alias(), name(), dp()}].
@@ -190,28 +193,28 @@ prefix_match(Pattern) when is_binary(Pattern) ->
%% @end
prefix_foldl(Prefix, F, Acc) ->
case ets_lookup(Prefix) of
- [] ->
- prefix_foldl(ets_next(Prefix), Prefix, byte_size(Prefix),
- F, Acc);
- [#alias{key = Key}] ->
- prefix_foldl(Key, Prefix, byte_size(Prefix), F, Acc)
+ [] ->
+ prefix_foldl(ets_next(Prefix), Prefix, byte_size(Prefix),
+ F, Acc);
+ [#alias{key = Key}] ->
+ prefix_foldl(Key, Prefix, byte_size(Prefix), F, Acc)
end.
prefix_foldl('$end_of_table', _, _, _, Acc) ->
Acc;
prefix_foldl(Key, Pattern, Sz, F, Acc) ->
case Key of
- <> ->
- case ets_lookup(Key) of
- [#alias{alias = Alias, entry = E, dp = DP}] ->
- prefix_foldl(ets_next(Key),
- Pattern, Sz, F,
- F(Alias, E, DP, Acc));
- _ ->
- prefix_foldl(ets_next(Key), Pattern, Sz, F, Acc)
- end;
- _ ->
- Acc
+ <> ->
+ case ets_lookup(Key) of
+ [#alias{alias = Alias, entry = E, dp = DP}] ->
+ prefix_foldl(ets_next(Key),
+ Pattern, Sz, F,
+ F(Alias, E, DP, Acc));
+ _ ->
+ prefix_foldl(ets_next(Key), Pattern, Sz, F, Acc)
+ end;
+ _ ->
+ Acc
end.
-spec prefix_foldr(binary(), fold_fun(), acc()) -> acc().
@@ -222,27 +225,27 @@ prefix_foldl(Key, Pattern, Sz, F, Acc) ->
%% @end
prefix_foldr(Pattern, F, Acc) ->
case ets_lookup(Pattern) of
- [] ->
- prefix_foldr(ets_next(Pattern), Pattern, byte_size(Pattern),
- F, Acc);
- [#alias{key = Key}] ->
- prefix_foldr(Key, Pattern, byte_size(Pattern), F, Acc)
+ [] ->
+ prefix_foldr(ets_next(Pattern), Pattern, byte_size(Pattern),
+ F, Acc);
+ [#alias{key = Key}] ->
+ prefix_foldr(Key, Pattern, byte_size(Pattern), F, Acc)
end.
prefix_foldr('$end_of_table', _, _, _, Acc) ->
Acc;
prefix_foldr(Key, Pattern, Sz, F, Acc) ->
case Key of
- <> ->
- case ets_lookup(Key) of
- [#alias{alias = Alias, entry = E, dp = DP}] ->
- F(Alias, E, DP, prefix_foldr(ets_next(Key),
- Pattern, Sz, F, Acc));
- _ ->
- prefix_foldr(ets_next(Key), Pattern, Sz, F, Acc)
- end;
- _ ->
- Acc
+ <> ->
+ case ets_lookup(Key) of
+ [#alias{alias = Alias, entry = E, dp = DP}] ->
+ F(Alias, E, DP, prefix_foldr(ets_next(Key),
+ Pattern, Sz, F, Acc));
+ _ ->
+ prefix_foldr(ets_next(Key), Pattern, Sz, F, Acc)
+ end;
+ _ ->
+ Acc
end.
-spec regexp_foldl(regexp(), fold_fun(), acc()) -> acc().
@@ -260,35 +263,35 @@ regexp_foldl(Regexp, F, Acc) when ?COMPILED_RE(Regexp) ->
regexp_foldl(Regexp, F, Acc) ->
Prefix = regexp_prefix(Regexp),
case ets_lookup(Prefix) of
- [] ->
- regexp_foldl(ets_next(Prefix), Prefix, byte_size(Prefix),
- re_compile(Regexp), F, Acc);
- [#alias{key = Key}] ->
- regexp_foldl(Key, Prefix, byte_size(Prefix),
- re_compile(Regexp), F, Acc)
+ [] ->
+ regexp_foldl(ets_next(Prefix), Prefix, byte_size(Prefix),
+ re_compile(Regexp), F, Acc);
+ [#alias{key = Key}] ->
+ regexp_foldl(Key, Prefix, byte_size(Prefix),
+ re_compile(Regexp), F, Acc)
end.
regexp_foldl('$end_of_table', _, _, _, _, Acc) ->
Acc;
regexp_foldl(Key, Prefix, Sz, Pattern, F, Acc) ->
case Key of
- <> ->
- case re:run(Key, Pattern) of
- {match, _} ->
- case ets_lookup(Key) of
- [#alias{alias = Alias, entry = E, dp = DP}] ->
- regexp_foldl(ets_next(Key), Prefix, Sz,
- Pattern, F, F(Alias, E, DP, Acc));
- _ ->
- regexp_foldl(ets_next(Key),
- Prefix, Sz, Pattern, F, Acc)
- end;
- nomatch ->
- regexp_foldl(ets_next(Key),
- Prefix, Sz, Pattern, F, Acc)
- end;
- _ ->
- Acc
+ <> ->
+ case re:run(Key, Pattern) of
+ {match, _} ->
+ case ets_lookup(Key) of
+ [#alias{alias = Alias, entry = E, dp = DP}] ->
+ regexp_foldl(ets_next(Key), Prefix, Sz,
+ Pattern, F, F(Alias, E, DP, Acc));
+ _ ->
+ regexp_foldl(ets_next(Key),
+ Prefix, Sz, Pattern, F, Acc)
+ end;
+ nomatch ->
+ regexp_foldl(ets_next(Key),
+ Prefix, Sz, Pattern, F, Acc)
+ end;
+ _ ->
+ Acc
end.
-spec regexp_foldr(regexp(), fold_fun(), acc()) -> acc().
@@ -306,37 +309,37 @@ regexp_foldr(Pattern, F, Acc) when ?COMPILED_RE(Pattern) ->
regexp_foldr(Pattern, F, Acc) ->
Prefix = regexp_prefix(Pattern),
case ets_lookup(Prefix) of
- [] ->
- regexp_foldr(ets_next(Prefix), Prefix, byte_size(Prefix),
- re_compile(Pattern),
- F, Acc);
- [#alias{key = Key}] ->
- regexp_foldr(Key, Prefix, byte_size(Prefix),
- re_compile(Pattern), F, Acc)
+ [] ->
+ regexp_foldr(ets_next(Prefix), Prefix, byte_size(Prefix),
+ re_compile(Pattern),
+ F, Acc);
+ [#alias{key = Key}] ->
+ regexp_foldr(Key, Prefix, byte_size(Prefix),
+ re_compile(Pattern), F, Acc)
end.
regexp_foldr('$end_of_table', _, _, _, _, Acc) ->
Acc;
regexp_foldr(Key, Prefix, Sz, Pattern, F, Acc) ->
case Key of
- <> ->
- case re:run(Key, Pattern) of
- {match, _} ->
- case ets_lookup(Key) of
- [#alias{alias = Alias, entry = E, dp = DP}] ->
- F(Alias, E, DP, regexp_foldr(ets_next(Key),
- Prefix, Sz, Pattern,
- F, Acc));
- _ ->
- regexp_foldr(ets_next(Key), Prefix, Sz,
- Pattern, F, Acc)
- end;
- nomatch ->
- regexp_foldr(ets_next(Key), Prefix, Sz,
- Pattern, F, Acc)
- end;
- _ ->
- Acc
+ <> ->
+ case re:run(Key, Pattern) of
+ {match, _} ->
+ case ets_lookup(Key) of
+ [#alias{alias = Alias, entry = E, dp = DP}] ->
+ F(Alias, E, DP, regexp_foldr(ets_next(Key),
+ Prefix, Sz, Pattern,
+ F, Acc));
+ _ ->
+ regexp_foldr(ets_next(Key), Prefix, Sz,
+ Pattern, F, Acc)
+ end;
+ nomatch ->
+ regexp_foldr(ets_next(Key), Prefix, Sz,
+ Pattern, F, Acc)
+ end;
+ _ ->
+ Acc
end.
just_acc(Alias, Entry, DP, Acc) ->
@@ -345,11 +348,11 @@ just_acc(Alias, Entry, DP, Acc) ->
start_link() ->
Tab = maybe_create_ets(),
case gen_server:start_link({local, ?MODULE}, ?MODULE, [], []) of
- {ok, Pid} = Res ->
- ets:give_away(Tab, Pid, give_away),
- Res;
- Other ->
- Other
+ {ok, Pid} = Res ->
+ ets:give_away(Tab, Pid, give_away),
+ Res;
+ Other ->
+ Other
end.
%% @private
@@ -360,25 +363,25 @@ init(_) ->
handle_call({new, Alias, Entry, DP}, _, St) ->
Key = to_key(Alias),
Res = case ets:member(?TAB, Key) of
- true ->
- {error, exists};
- false ->
- ets:insert(?TAB, #alias{key = Key, alias = Alias,
- entry = Entry, dp = DP}),
- ok
- end,
+ true ->
+ {error, exists};
+ false ->
+ ets:insert(?TAB, #alias{key = Key, alias = Alias,
+ entry = Entry, dp = DP}),
+ ok
+ end,
{reply, Res, St};
handle_call({load, F}, _, St) ->
Res = try do_load(F)
- catch
- error:R -> {error, R}
- end,
+ catch
+ error:R -> {error, R}
+ end,
{reply, Res, St};
handle_call({unload, F}, _, St) ->
Res = try do_unload(F)
- catch
- error:R -> {error, R}
- end,
+ catch
+ error:R -> {error, R}
+ end,
{reply, Res, St};
handle_call({delete, Alias}, _, St) ->
Key = to_key(Alias),
@@ -407,11 +410,11 @@ code_change(_, St, _) ->
maybe_create_ets() ->
case ets:info(?TAB, name) of
- undefined ->
- ets:new(?TAB, [ordered_set, named_table, public,
- {keypos, #alias.key}, {heir, self(), failover}]);
- _ ->
- ?TAB
+ undefined ->
+ ets:new(?TAB, [ordered_set, named_table, public,
+ {keypos, #alias.key}, {heir, self(), failover}]);
+ _ ->
+ ?TAB
end.
ets_lookup(Key) -> ets:lookup(?TAB, Key).
@@ -425,32 +428,112 @@ to_key(A) when is_binary(A) ->
do_load(F) ->
Map = F(),
+ check_map(Map),
lists:foreach(
fun({Entry, DPs}) when is_list(Entry), is_list(DPs) ->
- lists:foreach(
- fun({DP, Alias}) when is_atom(DP), is_atom(Alias);
- is_atom(DP), is_binary(Alias) ->
- Key = to_key(Alias),
- ets:insert(?TAB, #alias{key = Key,
- alias = Alias,
- entry = Entry,
- dp = DP})
- end, DPs)
+ lists:foreach(
+ fun({DP, Alias}) when is_atom(DP), is_atom(Alias);
+ is_atom(DP), is_binary(Alias) ->
+ Key = to_key(Alias),
+ true = ets:insert_new(?TAB, #alias{key = Key,
+ alias = Alias,
+ entry = Entry,
+ dp = DP})
+ end, DPs)
end, Map).
+check_map(Map) ->
+ case lists:foldl(
+ fun(F, {M1,Es}) ->
+ F(M1, Es)
+ end, {Map, []}, [fun check_args/2,
+ fun check_duplicates/2,
+ fun check_existing/2]) of
+ {Map1, []} ->
+ Map1;
+ {_, Errors} ->
+ error({map_error, Errors})
+ end.
+
+check_args(Map, Es) ->
+ Check = deep_fold(
+ fun({Alias, Entry, DP}, D) ->
+ case valid_arg(Alias, Entry, DP) of
+ true -> D;
+ false ->
+ orddict:append(Alias, {Entry, DP}, D)
+ end;
+ (Other, D) ->
+ orddict:append(unrecognized, Other, D)
+ end, orddict:new(), Map),
+ maybe_add_errors(Check, invalid, Map, Es).
+
+check_duplicates(Map, Es) ->
+ Check = deep_fold(
+ fun({Alias, Entry, DP}, D) ->
+ dict:append(Alias, {Entry,DP}, D);
+ (_, D) ->
+ D
+ end, dict:new(), Map),
+ %% We have duplicates if the value of any dict item is a list of length > 1.
+ Dups = dict:fold(fun(K, [_,_|_] = V, Acc) -> [{K, V}|Acc];
+ (_, _, Acc) -> Acc
+ end, [], Check),
+ maybe_add_errors(Dups, duplicate_aliases, Map, Es).
+
+check_existing(Map, Es) ->
+ Check = deep_fold(
+ fun({Alias, Entry, DP}, Acc) ->
+ %% Accept identical entries
+ case resolve(Alias) of
+ {Entry, DP} -> Acc;
+ error -> Acc;
+ {OtherEntry, OtherDP} ->
+ orddict:append(Alias, {OtherEntry, OtherDP}, Acc)
+ end;
+ (_, D) ->
+ D
+ end, orddict:new(), Map),
+ maybe_add_errors(Check, existing_aliases, Map, Es).
+
+valid_arg(Alias, Entry, DP)
+ when is_list(Entry), is_atom(DP), is_atom(Alias);
+ is_list(Entry), is_atom(DP), is_binary(Alias);
+ is_list(Entry), is_integer(DP), is_atom(Alias);
+ is_list(Entry), is_integer(DP), is_binary(Alias) -> true;
+valid_arg(_, _, _) ->
+ false.
+
+
+maybe_add_errors([], _, Map, Es) -> {Map, Es};
+maybe_add_errors([_|_] = NewErrors, Kind, Map, Es) ->
+ {Map, [{Kind, NewErrors}|Es]}.
+
+deep_fold(F, Acc, Map) ->
+ lists:foldl(
+ fun({Entry, DPs}, Acc1) ->
+ lists:foldl(
+ fun({DP, Alias}, Acc2) ->
+ F({Alias, Entry, DP}, Acc2)
+ end, Acc1, DPs);
+ (Other, Acc1) ->
+ %% Bad input, but let's pass it on to the check function
+ F(Other, Acc1)
+ end, Acc, Map).
+
do_unload(F) ->
Map = F(),
lists:foreach(
fun({Entry, DPs}) when is_list(Entry), is_list(DPs) ->
- lists:foreach(
- fun({DP, Alias}) when is_atom(Alias);
+ lists:foreach(
+ fun({DP, Alias}) when is_atom(Alias);
is_binary(Alias) ->
- Key = to_key(Alias),
- ets:delete_object(?TAB, #alias{key = Key,
+ Key = to_key(Alias),
+ ets:delete_object(?TAB, #alias{key = Key,
alias = Alias,
entry = Entry,
dp = DP})
- end, DPs)
+ end, DPs)
end, Map).
re_compile(R) ->
@@ -482,11 +565,11 @@ regexp_prefix_(<<>>, Acc) ->
alias_test_() ->
{setup,
fun() ->
- exometer:start(),
- create_entries(),
- load_aliases(),
- ets:tab2list(?TAB),
- ok
+ exometer:start(),
+ create_entries(),
+ load_aliases(),
+ ets:tab2list(?TAB),
+ ok
end,
fun(_) -> application:stop(exometer) end,
[?_test(t_resolve()),
@@ -532,7 +615,7 @@ t_get_value() ->
t_prefix_match() ->
?assertMatch(
[{<>, [?Pfx,g,1 ],value},
- {<>, [?Pfx,g,10],value}],
+ {<>, [?Pfx,g,10],value}],
prefix_match(<>)).
t_prefix_match2() ->
@@ -542,29 +625,29 @@ t_prefix_match2() ->
t_prefix_foldl() ->
?assertMatch(
[{<>, [?Pfx,g,10],value},
- {<>, [?Pfx,g,1 ],value}],
+ {<>, [?Pfx,g,1 ],value}],
prefix_foldl(<>,
- fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
+ fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
t_regexp_foldl() ->
?assertMatch(
[{<>,[?Pfx,g,5],value},
- {<>,[?Pfx,g,4],value},
- {<>,[?Pfx,g,3],value}],
+ {<>,[?Pfx,g,4],value},
+ {<>,[?Pfx,g,3],value}],
regexp_foldl(<<"^",?Pfx,"_g_[345]$">>,
- fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
+ fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
t_regexp_foldl2() ->
?assertMatch([], regexp_foldl(<<"^",?Pfx,"_g_[ab]$">>,
- fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
+ fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
t_regexp_foldr() ->
?assertMatch(
[{<>,[?Pfx,g,3],value},
- {<>,[?Pfx,g,4],value},
- {<>,[?Pfx,g,5],value}],
+ {<>,[?Pfx,g,4],value},
+ {<>,[?Pfx,g,5],value}],
regexp_foldr(<<"^",?Pfx,"_g_[345]$">>,
- fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
+ fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])).
t_unload() ->
ok = unload(fun test_aliases/0),
@@ -590,7 +673,7 @@ load_aliases() ->
test_aliases() ->
[{[?Pfx,g,N], [{value, iolist_to_binary([?Pfx, "_g_",
- integer_to_list(N)])}]}
+ integer_to_list(N)])}]}
|| N <- lists:seq(1,10)].
-endif.
diff --git a/src/exometer_cache.erl b/src/exometer_cache.erl
index eacc53a..620e8a2 100644
--- a/src/exometer_cache.erl
+++ b/src/exometer_cache.erl
@@ -16,7 +16,8 @@
-export([read/2, %% (Name, DataPoint) -> {ok, Value} | not_found
write/3, %% (Name, DataPoint, Value) -> ok
write/4, %% (Name, DataPoint, Value, TTL) -> ok
- delete/2
+ delete/2,
+ delete_name/1
]).
-export([init/1,
@@ -63,6 +64,14 @@ delete(Name, DataPoint) ->
%% Cancel the timer?
ets:delete(?TABLE, path(Name, DataPoint)).
+delete_name(Name) ->
+ %% This function currently doesn't cancel any timers. The cost of doing
+ %% so would outweigh the cost of reacting to any timers that happen to
+ %% fire (timer-based cleanup matches on TRef, so will not accidentally
+ %% delete the wrong entries.)
+ ets:match_delete(?TABLE, #cache{name = {Name,'_'}, _ = '_'}),
+ ok.
+
start_timer(Name, TTL, TS) ->
gen_server:cast(?MODULE, {start_timer, Name, TTL, TS}).
diff --git a/src/exometer_core.app.src b/src/exometer_core.app.src
index f7f549a..12d349a 100644
--- a/src/exometer_core.app.src
+++ b/src/exometer_core.app.src
@@ -8,7 +8,8 @@
[
kernel,
stdlib,
- lager
+ lager,
+ setup
]},
{included_applications,
[
diff --git a/src/exometer_core_sup.erl b/src/exometer_core_sup.erl
index 9b910a8..4405c2c 100644
--- a/src/exometer_core_sup.erl
+++ b/src/exometer_core_sup.erl
@@ -1,6 +1,6 @@
%% -------------------------------------------------------------------
%%
-%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2014-15 Basho Technologies, Inc. All Rights Reserved.
%%
%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -38,6 +38,7 @@ init([]) ->
?CHILD(exometer_admin, worker),
?CHILD(exometer_cache, worker),
?CHILD(exometer_report, worker),
+ ?CHILD(exometer_report_logger_sup, supervisor),
?CHILD(exometer_folsom_monitor, worker),
?CHILD(exometer_alias, worker)
],
diff --git a/src/exometer_cpu.erl b/src/exometer_cpu.erl
index 0c141b3..2f9001b 100644
--- a/src/exometer_cpu.erl
+++ b/src/exometer_cpu.erl
@@ -49,7 +49,7 @@ probe_init(_, _, Opts) ->
probe_terminate(_) -> ok.
probe_get_value(DPs, #st{data = Data0,
- datapoints = DPs0} = S) ->
+ datapoints = DPs0} = S) ->
Data1 = if Data0 == undefined -> sample(DPs0);
true -> Data0
end,
diff --git a/src/exometer_duration.erl b/src/exometer_duration.erl
index e4d57ff..097995a 100644
--- a/src/exometer_duration.erl
+++ b/src/exometer_duration.erl
@@ -13,18 +13,18 @@
%% exometer_entry callbacks
-export([new/3,
- delete/3,
- get_value/3,
- get_value/4,
- get_datapoints/3,
- setopts/3,
- update/4,
- reset/3,
- sample/3]).
+ delete/3,
+ get_value/3,
+ get_value/4,
+ get_datapoints/3,
+ setopts/3,
+ update/4,
+ reset/3,
+ sample/3]).
%% exometer_probe callbacks
-export([behaviour/0,
- probe_init/3,
+ probe_init/3,
probe_terminate/1,
probe_get_value/2,
probe_update/2,
@@ -38,10 +38,10 @@
-include("exometer.hrl").
-import(netlink_stat, [get_value/1]).
-record(st, {name,
- t_start,
- count = 0,
- last = 0,
- histogram,
+ t_start,
+ count = 0,
+ last = 0,
+ histogram,
opts = []}).
@@ -98,11 +98,11 @@ probe_get_datapoints(#st{histogram = H}) ->
probe_get_value(DataPoints, #st{histogram = H} = St) ->
case DataPoints -- ?DATAPOINTS of
- [] ->
- {ok, fill_datapoints(DataPoints, [], St)};
- HDPs ->
- {ok, HVals} = exometer_histogram:probe_get_value(HDPs, H),
- {ok, fill_datapoints(DataPoints, HVals, St)}
+ [] ->
+ {ok, fill_datapoints(DataPoints, [], St)};
+ HDPs ->
+ {ok, HVals} = exometer_histogram:probe_get_value(HDPs, H),
+ {ok, fill_datapoints(DataPoints, HVals, St)}
end.
probe_setopts(_Entry, _Opts, _St) ->
@@ -112,12 +112,12 @@ probe_update({timer_start, T}, St) ->
{ok, St#st{t_start = T}};
probe_update({timer_end, T}, #st{histogram = H, count = C} = St) ->
try
- Duration = timer:now_diff(T, St#st.t_start),
- {ok, H1} = exometer_histogram:probe_update(Duration, H),
- {ok, St#st{histogram = H1, count = C+1, last = Duration}}
+ Duration = timer:now_diff(T, St#st.t_start),
+ {ok, H1} = exometer_histogram:probe_update(Duration, H),
+ {ok, St#st{histogram = H1, count = C+1, last = Duration}}
catch
- error:_ ->
- {ok, St}
+ error:_ ->
+ {ok, St}
end.
probe_reset(#st{histogram = H} = St) ->
diff --git a/src/exometer_folsom.erl b/src/exometer_folsom.erl
index b1f1db4..5bffc26 100644
--- a/src/exometer_folsom.erl
+++ b/src/exometer_folsom.erl
@@ -12,7 +12,7 @@
-behaviour(exometer_entry).
-export([behaviour/0,
- new/3,
+ new/3,
delete/3,
get_datapoints/3,
get_value/4,
@@ -47,8 +47,8 @@ new(Name, duration, Opts) ->
{folsom_metrics:new_duration(Name), opt_ref(Opts)};
new(Name, history, Opts) ->
case lists:keyfind(size, 1, Opts) of
- {_, Sz} -> {folsom_metrics:new_history(Name, Sz), opt_ref(Opts)};
- false -> {folsom_metrics:new_history(Name), opt_ref(Opts)}
+ {_, Sz} -> {folsom_metrics:new_history(Name, Sz), opt_ref(Opts)};
+ false -> {folsom_metrics:new_history(Name), opt_ref(Opts)}
end.
@@ -81,30 +81,30 @@ reset(_, _, _) ->
get_value(Name, history, _Ref, DataPoints0) ->
try DataPoints = datapoints(history, DataPoints0),
- lists:foldr(
- fun(events, Acc) ->
- [{events, just_events(
- folsom_metrics_history:get_events(Name))}
- | Acc];
- (values, Acc) ->
- [{values, folsom_metrics_history:get_events(Name)}
- | Acc];
- (timed_events, Acc) ->
- [{timed_events,
- timed_events(
- folsom_metrics_history:get_events(Name))}
- | Acc];
- (Sz, Acc) when is_integer(Sz), Sz > 0 ->
- [{Sz, just_events(
- folsom_metrics_history:get_events(Name, Sz))}
- | Acc];
- (info, Acc) ->
- [{info, folsom_metrics_history:get_value(Name)}
- | Acc];
- (_, Acc) -> Acc
- end, [], DataPoints)
+ lists:foldr(
+ fun(events, Acc) ->
+ [{events, just_events(
+ folsom_metrics_history:get_events(Name))}
+ | Acc];
+ (values, Acc) ->
+ [{values, folsom_metrics_history:get_events(Name)}
+ | Acc];
+ (timed_events, Acc) ->
+ [{timed_events,
+ timed_events(
+ folsom_metrics_history:get_events(Name))}
+ | Acc];
+ (Sz, Acc) when is_integer(Sz), Sz > 0 ->
+ [{Sz, just_events(
+ folsom_metrics_history:get_events(Name, Sz))}
+ | Acc];
+ (info, Acc) ->
+ [{info, folsom_metrics_history:get_value(Name)}
+ | Acc];
+ (_, Acc) -> Acc
+ end, [], DataPoints)
catch
- error:_ -> unavailable
+ error:_ -> unavailable
end;
get_value(Name, Type, Ref, DataPoints) ->
Trunc = get_trunc_opt(Ref),
diff --git a/src/exometer_folsom_monitor.erl b/src/exometer_folsom_monitor.erl
index b239234..a166552 100644
--- a/src/exometer_folsom_monitor.erl
+++ b/src/exometer_folsom_monitor.erl
@@ -24,12 +24,12 @@
-behaviour(gen_server).
-export([start_link/0,
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3]).
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
-export([monitor/2]).
-export([hook/1]).
@@ -78,9 +78,9 @@ start_link() ->
%% @private
init(_) ->
Mon = lists:foldl(
- fun({Mf, Mc}, D) ->
- orddict:append(Mf, Mc, D)
- end, orddict:new(), find_env()),
+ fun({Mf, Mc}, D) ->
+ orddict:append(Mf, Mc, D)
+ end, orddict:new(), find_env()),
init_monitor(Mon),
{ok, #st{mon = Mon}}.
@@ -89,7 +89,7 @@ find_env() ->
E2 = exometer_util:get_env(folsom_monitor, []),
lists:flatmap(
fun({_,_} = M) -> [M];
- (L) when is_list(L) -> L
+ (L) when is_list(L) -> L
end, E1 ++ E2).
%% @private
@@ -120,20 +120,20 @@ init_monitor([_|_]) ->
do_init_monitor() ->
case is_transformed() of
- true ->
- lager:debug("already transformed...~n", []),
- ok;
- false ->
- lager:debug("transforming folsom_metrics...~n", []),
- parse_trans_mod:transform_module(folsom_metrics, fun pt/2, [])
+ true ->
+ lager:debug("already transformed...~n", []),
+ ok;
+ false ->
+ lager:debug("transforming folsom_metrics...~n", []),
+ parse_trans_mod:transform_module(folsom_metrics, fun pt/2, [])
end.
pt(Forms, _) ->
Funcs = funcs(),
NewForms = parse_trans:plain_transform(
- fun(F) ->
- plain_pt(F, Funcs)
- end, Forms),
+ fun(F) ->
+ plain_pt(F, Funcs)
+ end, Forms),
mark_transformed(NewForms).
is_transformed() ->
@@ -147,10 +147,10 @@ mark_transformed([H|T]) ->
plain_pt({function,L,F,A,Cs}, Funcs) ->
case lists:keyfind({F,A}, 1, Funcs) of
- {_, Type} ->
- {function,L,F,A,insert_hook(Type, Cs)};
- false ->
- continue
+ {_, Type} ->
+ {function,L,F,A,insert_hook(Type, Cs)};
+ false ->
+ continue
end;
plain_pt(_, _) ->
continue.
@@ -169,10 +169,10 @@ funcs() ->
insert_hook(Type, Cs) ->
lists:map(
fun({clause,L0,Args,Gs,Body}) ->
- L = element(2,hd(Body)),
- {clause,L0,Args,Gs,
- [{call,L,{remote,L,{atom,L,?MODULE},{atom,L,hook}},
- [cons([{atom,L,Type}|Args], L)]}|Body]}
+ L = element(2,hd(Body)),
+ {clause,L0,Args,Gs,
+ [{call,L,{remote,L,{atom,L,?MODULE},{atom,L,hook}},
+ [cons([{atom,L,Type}|Args], L)]}|Body]}
end, Cs).
cons([H|T], L) -> {cons,L,H,cons(T,L)};
@@ -181,32 +181,32 @@ cons([] , L) -> {nil,L}.
check_stack(Mon, Stack, Args) ->
orddict:fold(
fun('_', CBs, Acc) ->
- _ = [maybe_create(CB, Args) || CB <- CBs],
- Acc;
- (Mod, CBs, Acc) ->
- case lists:keymember(Mod, 1, Stack) of
- true ->
- _ = [maybe_create(CB, Args) || CB <- CBs];
- false ->
- ignore
- end,
- Acc
+ _ = [maybe_create(CB, Args) || CB <- CBs],
+ Acc;
+ (Mod, CBs, Acc) ->
+ case lists:keymember(Mod, 1, Stack) of
+ true ->
+ _ = [maybe_create(CB, Args) || CB <- CBs];
+ false ->
+ ignore
+ end,
+ Acc
end, ok, Mon).
maybe_create(CB, [FolsomType, Name | Args]) ->
try CB:copy_folsom(Name, FolsomType, Args) of
- {ExoName, ExoType, ExoArgs} ->
- exometer:new(ExoName, ExoType, ExoArgs);
- L when is_list(L) ->
- lists:foreach(
- fun({ExoName, ExoType, ExoArgs}) ->
- exometer:new(ExoName, ExoType, ExoArgs)
- end, L);
- false ->
- ignore
+ {ExoName, ExoType, ExoArgs} ->
+ exometer:new(ExoName, ExoType, ExoArgs);
+ L when is_list(L) ->
+ lists:foreach(
+ fun({ExoName, ExoType, ExoArgs}) ->
+ exometer:new(ExoName, ExoType, ExoArgs)
+ end, L);
+ false ->
+ ignore
catch
- Cat:Msg ->
- lager:error("~p:copy_folsom(~p,~p,~p): ~p:~p~n",
- [CB, Name, FolsomType, Args, Cat, Msg]),
- ignore
+ Cat:Msg ->
+ lager:error("~p:copy_folsom(~p,~p,~p): ~p:~p~n",
+ [CB, Name, FolsomType, Args, Cat, Msg]),
+ ignore
end.
diff --git a/src/exometer_function.erl b/src/exometer_function.erl
index b1fe197..07d9d24 100644
--- a/src/exometer_function.erl
+++ b/src/exometer_function.erl
@@ -13,7 +13,7 @@
-behaviour(exometer_entry).
-export([behaviour/0,
- new/3,
+ new/3,
update/4,
reset/3,
get_value/4,
@@ -45,11 +45,11 @@
arg_spec(), res_type(), datapoints()}.
-type fun_spec() :: simple_fun() | extended_fun().
-type fun_rep() :: {mod_name(), fun_name()}
- | {mod_name(), fun_name(), each | once,
- arg_spec(), res_type(), datapoints()}
- | {mod_name(), fun_name(), each | once,
- arg_spec(), match, any()}
- | {eval, [expr()], datapoints()}.
+ | {mod_name(), fun_name(), each | once,
+ arg_spec(), res_type(), datapoints()}
+ | {mod_name(), fun_name(), each | once,
+ arg_spec(), match, any()}
+ | {eval, [expr()], datapoints()}.
-spec behaviour() -> exometer:behaviour().
behaviour() ->
@@ -257,7 +257,7 @@ actual_datapoints(DPs0, DPs, _) ->
get_datapoints(_Name, _Type, {_, _, once, _, match, Pat}) ->
pattern_datapoints(Pat);
get_datapoints(_Name, _Type, T) when is_tuple(T), is_list(
- element(size(T),T)) ->
+ element(size(T),T)) ->
element(size(T), T);
get_datapoints(_Name, _Type, _Ref) ->
[value].
@@ -580,20 +580,20 @@ e({element,E,T}, Bs) ->
end;
e({histogram, Vs}, Bs) ->
case e(Vs, Bs) of
- L when is_list(L) ->
- exometer_util:histogram(L);
- _ -> error(badarg)
+ L when is_list(L) ->
+ exometer_util:histogram(L);
+ _ -> error(badarg)
end;
e({histogram, Vs, DPs}, Bs) ->
case e(Vs, Bs) of
- L when is_list(L) ->
- DataPoints = case e(DPs, Bs) of
- default -> default;
- D when is_list(D) -> D;
- _ -> error(badarg)
- end,
- exometer_util:histogram(L, DataPoints);
- _ -> error(badarg)
+ L when is_list(L) ->
+ DataPoints = case e(DPs, Bs) of
+ default -> default;
+ D when is_list(D) -> D;
+ _ -> error(badarg)
+ end,
+ exometer_util:histogram(L, DataPoints);
+ _ -> error(badarg)
end;
e({fold,Vx,Va,Es,Ea,El}, Bs) when is_atom(Vx), is_atom(Va) ->
case e(El, Bs) of
diff --git a/src/exometer_global.erl b/src/exometer_global.erl
new file mode 100644
index 0000000..e30c1a4
--- /dev/null
+++ b/src/exometer_global.erl
@@ -0,0 +1,17 @@
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+%% @private
+-module(exometer_global).
+
+-export([status/0]).
+
+-spec status() -> enabled | disabled.
+status() ->
+ enabled.
diff --git a/src/exometer_histogram.erl b/src/exometer_histogram.erl
index e4fdbd8..a6116b6 100644
--- a/src/exometer_histogram.erl
+++ b/src/exometer_histogram.erl
@@ -60,25 +60,25 @@
%% exometer_probe callbacks
-export([behaviour/0,
- probe_init/3,
- probe_terminate/1,
- probe_setopts/3,
- probe_update/2,
- probe_get_value/2,
- probe_get_datapoints/1,
- probe_reset/1,
- probe_code_change/3,
- probe_sample/1,
- probe_handle_msg/2]).
+ probe_init/3,
+ probe_terminate/1,
+ probe_setopts/3,
+ probe_update/2,
+ probe_get_value/2,
+ probe_get_datapoints/1,
+ probe_reset/1,
+ probe_code_change/3,
+ probe_sample/1,
+ probe_handle_msg/2]).
-compile(inline).
-compile(inline_list_funcs).
-export([datapoints/0]).
-export([average_sample/3,
- average_transform/2]).
+ average_transform/2]).
-export([test_run/1, test_run/2,
- test_series/0]).
+ test_series/0]).
%% -compile({parse_transform, exometer_igor}).
%% -compile({igor, [{files, ["src/exometer_util.erl"
@@ -95,7 +95,7 @@
time_span = 60000, %% msec
truncate = true,
histogram_module = exometer_slot_slide,
- heap,
+ heap,
opts = []}).
%% for auto-conversion
@@ -109,30 +109,30 @@ behaviour() ->
probe.
probe_init(Name, _Type, Options) ->
- {ok, init_state(Name, Options)}.
+ {ok, init_state(Name, Options)}.
init_state(Name, Options) ->
St = process_opts(#st{name = Name},
- [{histogram_module, exometer_slot_slide},
- {time_span, 60000},
- {slot_period, 10}] ++ Options),
+ [{histogram_module, exometer_slot_slide},
+ {time_span, 60000},
+ {slot_period, 10}] ++ Options),
Slide = (St#st.histogram_module):new(St#st.time_span,
- St#st.slot_period,
- fun average_sample/3,
- fun average_transform/2,
- Options),
+ St#st.slot_period,
+ fun average_sample/3,
+ fun average_transform/2,
+ Options),
Heap = if St#st.histogram_module == exometer_slot_slide ->
- case lists:keyfind(keep_high, 1, Options) of
- false ->
- undefined;
- {_, N} when is_integer(N), N > 0 ->
- T = exometer_shallowtree:new(N),
- {T, T};
- {_, 0} ->
- undefined
- end;
- true -> undefined
- end,
+ case lists:keyfind(keep_high, 1, Options) of
+ false ->
+ undefined;
+ {_, N} when is_integer(N), N > 0 ->
+ T = exometer_shallowtree:new(N),
+ {T, T};
+ {_, 0} ->
+ undefined
+ end;
+ true -> undefined
+ end,
St#st{slide = Slide, heap = Heap}.
@@ -164,26 +164,26 @@ get_value_int(St, DataPoints) ->
get_value_int_(#st{truncate = Trunc,
histogram_module = Module,
- time_span = TimeSpan,
- heap = Heap} = St, DataPoints) ->
+ time_span = TimeSpan,
+ heap = Heap} = St, DataPoints) ->
%% We need element count and sum of all elements to get mean value.
Tot0 = case Trunc of true -> 0; round -> 0; false -> 0.0 end,
TS = exometer_util:timestamp(),
{Length, FullLength, Total, Min0, Max, Lst0, Xtra} =
Module:foldl(
- TS,
+ TS,
fun
({_TS1, {Val, Cnt, NMin, NMax, X}},
{Length, FullLen, Total, OMin, OMax, List, Xs}) ->
- {Length + 1, FullLen + Cnt, Total + Val,
- min(OMin, NMin), max(OMax, NMax),
- [Val|List], [X|Xs]};
+ {Length + 1, FullLen + Cnt, Total + Val,
+ min(OMin, NMin), max(OMax, NMax),
+ [Val|List], [X|Xs]};
({_TS1, Val}, {Length, _, Total, Min, Max, List, Xs}) ->
- L1 = Length+1,
- {L1, L1, Total + Val, min(Val, Min), max(Val, Max),
- [Val|List], Xs}
- end,
+ L1 = Length+1,
+ {L1, L1, Total + Val, min(Val, Min), max(Val, Max),
+ [Val|List], Xs}
+ end,
{0, 0, Tot0, infinity, 0, [], []}, St#st.slide),
Min = if Min0 == infinity -> 0; true -> Min0 end,
Mean = case Length of
@@ -205,23 +205,23 @@ get_value_int_(#st{truncate = Trunc,
get_from_heap({New,Old}, TS, TSpan, N, DPs) when N > 0 ->
Sz = exometer_shallowtree:size(New)
- + exometer_shallowtree:size(Old),
+ + exometer_shallowtree:size(Old),
if Sz > 0 ->
- MinPerc = 100 - ((Sz*100) div N),
- MinPerc10 = MinPerc * 10,
- GetDPs = lists:foldl(
- fun(D, Acc) when is_integer(D),
- D < 100, D >= MinPerc ->
- [{D, p(D, N)}|Acc];
- (D, Acc) when is_integer(D),
- D > 100, D >= MinPerc10 ->
- [{D, p(D, N)}|Acc];
- (_, Acc) ->
- Acc
- end, [], DPs),
- pick_heap_vals(GetDPs, New, Old, TS, TSpan);
+ MinPerc = 100 - ((Sz*100) div N),
+ MinPerc10 = MinPerc * 10,
+ GetDPs = lists:foldl(
+ fun(D, Acc) when is_integer(D),
+ D < 100, D >= MinPerc ->
+ [{D, p(D, N)}|Acc];
+ (D, Acc) when is_integer(D),
+ D > 100, D >= MinPerc10 ->
+ [{D, p(D, N)}|Acc];
+ (_, Acc) ->
+ Acc
+ end, [], DPs),
+ pick_heap_vals(GetDPs, New, Old, TS, TSpan);
true ->
- []
+ []
end;
get_from_heap(_, _, _, _, _) ->
[].
@@ -232,13 +232,13 @@ pick_heap_vals(DPs, New, Old, TS, TSpan) ->
TS0 = TS - TSpan,
NewVals = exometer_shallowtree:filter(fun(V,_) -> {true,V} end, New),
OldVals = exometer_shallowtree:filter(
- fun(V,T) ->
- if T >= TS0 ->
- {true, V};
- true ->
- false
- end
- end, Old),
+ fun(V,T) ->
+ if T >= TS0 ->
+ {true, V};
+ true ->
+ false
+ end
+ end, Old),
Vals = revsort(OldVals ++ NewVals),
exometer_util:pick_items(Vals, DPs).
@@ -246,18 +246,13 @@ revsort(L) ->
lists:sort(fun erlang:'>'/2, L).
p(50, N) -> perc(0.5, N);
-p(75, N) -> perc(0.25, N);
-p(90, N) -> perc(0.1, N);
-p(95, N) -> perc(0.05, N);
-p(99, N) -> perc(0.01, N);
-p(999,N) -> perc(0.001, N).
-
-perc(P, Len) when P > 1.0 ->
- round((P / 10) * Len) + 1;
-perc(P, Len) ->
- round(P * Len) + 1.
-
+p(75, N) -> perc(0.75, N);
+p(90, N) -> perc(0.9, N);
+p(95, N) -> perc(0.95, N);
+p(99, N) -> perc(0.99, N);
+p(999,N) -> perc(0.999, N).
+perc(P, N) -> N - exometer_util:perc(P, N) + 1.
add_extra(Length, L, []) ->
{Length, L};
@@ -300,11 +295,16 @@ probe_setopts(_Entry, _Opts, _St) ->
probe_update(Value, ?OLDSTATE = St) ->
probe_update(Value, convert(St));
probe_update(Value, St) ->
- {ok, update_int(exometer_util:timestamp(), Value, St)}.
+ if is_number(Value) ->
+ {ok, update_int(exometer_util:timestamp(), Value, St)};
+ true ->
+ %% ignore
+ {ok, St}
+ end.
update_int(Timestamp, Value, #st{slide = Slide,
- histogram_module = Module,
- heap = Heap} = St) ->
+ histogram_module = Module,
+ heap = Heap} = St) ->
{Wrapped, Slide1} = Module:add_element(Timestamp, Value, Slide, true),
St#st{slide = Slide1, heap = into_heap(Wrapped, Value, Timestamp, Heap)}.
@@ -320,7 +320,7 @@ into_heap(true, Val, TS, {New,_}) ->
probe_reset(?OLDSTATE = St) ->
probe_reset(convert(St));
probe_reset(#st{slide = Slide,
- histogram_module = Module} = St) ->
+ histogram_module = Module} = St) ->
{ok, St#st{slide = Module:reset(Slide)}}.
probe_sample(_St) ->
@@ -335,10 +335,10 @@ probe_code_change(_, S, _) ->
{ok, S}.
convert({st, Name, Slide, Slot_period, Time_span,
- Truncate, Histogram_module, Opts}) ->
+ Truncate, Histogram_module, Opts}) ->
#st{name = Name, slide = Slide, slot_period = Slot_period,
- time_span = Time_span, truncate = Truncate,
- histogram_module = Histogram_module, opts = Opts}.
+ time_span = Time_span, truncate = Truncate,
+ histogram_module = Histogram_module, opts = Opts}.
process_opts(St, Options) ->
exometer_proc:process_options(Options),
@@ -349,13 +349,13 @@ process_opts(St, Options) ->
( {slot_period, Val}, St1) -> St1#st {slot_period = Val};
( {histogram_module, Val}, St1) -> St1#st {histogram_module = Val};
( {truncate, Val}, St1) when is_boolean(Val); Val == round ->
- St1#st{truncate = Val};
+ St1#st{truncate = Val};
%% Unknown option, pass on to State options list, replacing
%% any earlier versions of the same option.
({Opt, Val}, St1) ->
- St1#st{ opts = [ {Opt, Val}
- | lists:keydelete(Opt, 1, St1#st.opts) ] }
- end, St, Options).
+ St1#st{ opts = [ {Opt, Val}
+ | lists:keydelete(Opt, 1, St1#st.opts) ] }
+ end, St, Options).
-record(sample, {count, total, min, max, extra = []}).
%% Simple sample processor that maintains an average
@@ -436,13 +436,13 @@ test_run(Module, Interval) ->
test_run(Module, Int, Series) ->
St = test_new(test_opts(Module)),
{T1, St1} = tc(fun() ->
- test_update(
- Series, Int,
- exometer_util:timestamp(), St)
- end),
+ test_update(
+ Series, Int,
+ exometer_util:timestamp(), St)
+ end),
{T2, Result} = tc(fun() ->
- get_value_int(St1, default)
- end),
+ get_value_int(St1, default)
+ end),
erlang:garbage_collect(), erlang:yield(),
{T1, T2, Result}.
@@ -494,14 +494,14 @@ tc(F) ->
%% @end
test_series() ->
S = lists:flatten(
- [dupl(200,3),
- dupl(100,4),
- dupl(200,5),
- dupl(100,6),
- dupl(200,7),
- dupl(100,8),
- dupl(80,9),
- dupl(15,50), 80,81,82,83,100]),
+ [dupl(200,3),
+ dupl(100,4),
+ dupl(200,5),
+ dupl(100,6),
+ dupl(200,7),
+ dupl(100,8),
+ dupl(80,9),
+ dupl(15,50), 80,81,82,83,100]),
shuffle(S ++ S ++ S ++ S ++ S ++ S ++ S ++ S ++ S).
dupl(N,V) ->
diff --git a/src/exometer_igor.erl b/src/exometer_igor.erl
index f4668b4..3fd4100 100644
--- a/src/exometer_igor.erl
+++ b/src/exometer_igor.erl
@@ -8,8 +8,8 @@ parse_transform(Forms, Opts) ->
Includes = [I || {i,I} <- Opts],
NewForms =
igor:parse_transform(
- Forms, [{igor, IgorOpts ++ [{includes, Includes},
- {preprocess, true}]}|Opts]),
+ Forms, [{igor, IgorOpts ++ [{includes, Includes},
+ {preprocess, true}]}|Opts]),
fix_for_r16b03(NewForms).
%% erl_syntax:revert/1 is horribly broken in R16B03. This transform
diff --git a/src/exometer_info.erl b/src/exometer_info.erl
index c5ef43c..f6d2235 100644
--- a/src/exometer_info.erl
+++ b/src/exometer_info.erl
@@ -20,10 +20,10 @@
-module(exometer_info).
-export([status/1,
- pp/1,
- pp_lookup/1,
- pp_find/1,
- pp_select/1]).
+ pp/1,
+ pp_lookup/1,
+ pp_find/1,
+ pp_select/1]).
-include("exometer.hrl").
-include_lib("parse_trans/include/exprecs.hrl").
@@ -74,10 +74,10 @@ pp(X) ->
%% @end
pp_lookup(Name) ->
case exometer:info(Name, entry) of
- undefined ->
- undefined;
- Entry ->
- pp(Entry)
+ undefined ->
+ undefined;
+ Entry ->
+ pp(Entry)
end.
-spec pp_find(list()) -> [pp()].
diff --git a/src/exometer_probe.erl b/src/exometer_probe.erl
index eee16da..c846aff 100644
--- a/src/exometer_probe.erl
+++ b/src/exometer_probe.erl
@@ -376,12 +376,36 @@
%% NewState}', where `NewState' contains the new state of the probe
%% that reflects the processed message.
%%
-
+%% == Fault tolerance ==
+%% Probes are supervised by the `exometer_admin' process, and can be restarted
+%% after a crash. Restart parameters are provided via the option
+%% `{restart, Params}', where `Params' is a list of `{Frequency, Action}'
+%% tuples. `Frequency' is either `{Count, MilliSecs}' or ``'_''', and
+%% the corresponding `Action :: restart | disable | delete' will be performed
+%% if the frequency of restarts falls within the given limit.
+%%
+%% For example, `[{{3, 1000}, restart}]' will allow 3 restarts within a 1-second
+%% window. The matching is performed from top to bottom, and the first matching
+%% pattern is acted upon. If no matching pattern is found, the default action
+%% is `delete'. A pattern ``{'_', Action}'' acts as a catch-all, and should
+%% be put last in the list.
+%%
+%% It is also possible to specify ``{{Count, '_'}, Action}'', which means
+%% that a total of `Count' restarts is permitted, regardless of how far apart
+%% they are. The count is reset after each restart.
+%%
+%% Example:
+%%
+%% {restart, [{{3,1000}, restart}, % up to 3 restarts within 1 sec
+%% {{4,'_'} , disable}, % a total of up to 4 restarts
+%% {'_' , delete}]} % anything else
+%%
+%% @end
-module(exometer_probe).
-behaviour(exometer_entry).
-% exometer_entry callb
+%% exometer_entry callbacks
-export(
[
behaviour/0,
@@ -395,8 +419,17 @@
setopts/3
]).
+-export([start_probe/1,
+ stop_probe/1]).
+
+-export([restart/4]).
+
-include("exometer.hrl").
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
-record(st, {
name,
type,
@@ -413,13 +446,13 @@
-type mod_state() :: any().
-type data_points() :: [atom()].
-type probe_reply() :: ok
- | {ok, mod_state()}
- | {ok, any(), mod_state()}
- | {noreply, mod_state()}
- | {error, any()}.
+ | {ok, mod_state()}
+ | {ok, any(), mod_state()}
+ | {noreply, mod_state()}
+ | {error, any()}.
-type probe_noreply() :: ok
- | {ok, mod_state()}
- | {error, any()}.
+ | {ok, mod_state()}
+ | {error, any()}.
-callback behaviour() -> exometer:behaviour().
-callback probe_init(name(), type(), options()) -> probe_noreply().
@@ -437,26 +470,124 @@
-callback probe_code_change(any(), mod_state(), any()) -> {ok, mod_state()}.
new(Name, Type, [{arg, Module}|Opts]) ->
+ Restart = proplists:get_value(restart, Opts, default_restart()),
+ SpawnOpts = proplists:get_value(spawn_opts, Opts, spawn_opts()),
{ ok, exometer_proc:spawn_process(
- Name, fun() ->
- init(Name, Type, Module, Opts)
- end)
+ Name, fun() ->
+ init(Name, Type, Module, Opts)
+ end, proc_opts(Name, Module, Restart, SpawnOpts))
};
-
new(Name, Type, Options) ->
%% Extract the module to use.
{value, { module, Module }, Opts1 } = lists:keytake(module, 1, Options),
new(Name, Type, [{arg, Module} | Opts1]).
+proc_opts(Name, Module, Restart, SpawnOpts) when is_list(Restart) ->
+ proc_opts_(Name, Module, on_error_init(Restart), SpawnOpts);
+proc_opts(Name, Module, Restart, SpawnOpts) ->
+ proc_opts_(Name, Module, Restart, SpawnOpts).
+
+proc_opts_(Name, Module, Restart, SpawnOpts) ->
+ OnError = on_error(Name, Module, Restart, SpawnOpts),
+ [{on_error, OnError}, {spawn_opts, SpawnOpts}].
+
+
+spawn_opts() ->
+ [{fullsweep_after, 10}].
+
+on_error_init(R) when is_list(R) ->
+ {0, [], max_time(R), R}.
+
+on_error(Name, Module, R, SpawnOpts) ->
+ {restart, {?MODULE, restart, [Name, Module, R, SpawnOpts]}}.
+
+check_restart(Restart) ->
+ TS = exometer_util:timestamp(),
+ check_restart(TS, Restart).
+
+check_restart(TS, {Total, Hist, MaxT, R}) when is_integer(MaxT) ->
+ NewTotal = Total + 1,
+ Oldest = TS - MaxT,
+ NewHist = [TS | [T || T <- Hist,
+ T > Oldest]],
+ {action(NewTotal, NewHist, MaxT, R), {NewTotal, NewHist, MaxT, R}};
+check_restart(_TS, {Total, Hist, '_', R}) ->
+ NewTotal = Total + 1,
+ {action(NewTotal, [], '_', R), {NewTotal, Hist, '_', R}}.
+
+
+max_time([{{_,T},_}|Rest]) ->
+ max_time(Rest, T);
+max_time([{_,_}|Rest]) ->
+ max_time(Rest);
+max_time([]) ->
+ '_'.
+
+max_time([{{_,T},_}|Rest], Max) when is_integer(T) ->
+ max_time(Rest, erlang:max(T, Max));
+max_time([_|Rest], Max) ->
+ max_time(Rest, Max);
+max_time([], Max) ->
+ Max.
+
+action(_Total, _Hist, _MaxT, [{'_', Action}|_]) ->
+ Action;
+action(Total, Hist, MaxT, [{{N,'_'}, Action}|R]) ->
+ if N >= Total -> Action;
+ true -> action(Total, Hist, MaxT, R)
+ end;
+action(Total, Hist, MaxT, [{{N,T}, Action}|R]) ->
+ case within(Hist, N, T) of
+ true -> Action;
+ false ->
+ action(Total, Hist, MaxT, R)
+ end;
+action(_, _, _, []) ->
+ delete.
+
+within([T|Ts], N, Tm) ->
+ within(Ts, 1, N, T - Tm);
+within([], _, _) ->
+ false.
+
+within([H|T], N, M, Lim) when H > Lim ->
+ N1 = N + 1,
+ if N1 =< M ->
+ within(T, N1, M, Lim);
+ true ->
+ false
+ end;
+within(_, _, _, _) ->
+ true.
+
+restart(Name, Module, Error, SpawnOpts) ->
+ Type = exometer:info(Name, type),
+ Opts = exometer:info(Name, options),
+ case check_restart(Error) of
+ {restart, Error1} ->
+ {ok, exometer_proc:spawn_process(
+ Name, fun() ->
+ init(Name, Type, Module, Opts)
+ end,
+ proc_opts(Name, Module, Error1, SpawnOpts))};
+ {Other, _} when Other==delete; Other==disable ->
+ Other
+ end.
+
+default_restart() ->
+ [{{3, 60000}, restart},
+ {'_', disable}].
+
%% Should never be called directly for exometer_probe.
behaviour() ->
entry.
+delete(_Name, _Type, undefined) ->
+ ok;
delete(_Name, _Type, Pid) when is_pid(Pid) ->
exometer_proc:cast(Pid, delete).
-
get_value(_Name, _Type, Pid) when is_pid(Pid) ->
exometer_proc:call(Pid, {get_value, default}).
@@ -478,6 +609,37 @@ reset(_Name, _Type, Pid) when is_pid(Pid) ->
sample(_Name, _Type, Pid) when is_pid(Pid) ->
exometer_proc:cast(Pid, sample).
+
+%% === housekeeping functions used e.g. at enable/disable
+
+%% @private
+stop_probe(#exometer_entry{name = Name,
+ type = Type,
+ ref = Ref}) ->
+ exometer_cache:delete_name(Name),
+ if is_pid(Ref) ->
+ try exometer_probe:delete(Name, Type, Ref)
+ catch
+ error:_ ->
+ kill_probe(Ref)
+ end;
+ true ->
+ ok
+ end.
+
+kill_probe(Ref) when is_pid(Ref) ->
+ exometer_admin:demonitor(Ref),
+ exit(Ref, kill).
+
+%% @private
+start_probe(#exometer_entry{module = Module,
+ name = Name,
+ type = Type,
+ options = Opts}) ->
+ new(Name, Type, [{ arg, Module} | Opts ]).
+
+%% == Probe implementation
+
init(Name, Type, Mod, Opts) ->
process_flag(min_heap_size, 40000),
{St0, Opts1} = process_opts(Opts, #st{name = Name,
@@ -487,14 +649,14 @@ init(Name, Type, Mod, Opts) ->
%% Create a new state for the module
case {Mod:probe_init(Name, Type, St#st.opts),
- St#st.sample_interval} of
+ St#st.sample_interval} of
{ ok, infinity} ->
%% No sample timer to start. Return with undefined mod state
- loop(St#st{ mod_state = undefined });
+ loop(St#st{ mod_state = undefined });
{{ok, ModSt}, infinity} ->
%% No sample timer to start. Return with the mod state returned by probe_init.
- loop(St#st{ mod_state = ModSt });
+ loop(St#st{ mod_state = ModSt });
{ok, _} ->
%% Fire up the timer, with undefined mod
@@ -505,7 +667,7 @@ init(Name, Type, Mod, Opts) ->
loop(sample(St#st{ mod_state = ModSt }));
{{error, Reason}, _} ->
- %% FIXME: Proper shutdown.
+ %% FIXME: Proper shutdown.
{error, Reason}
end.
@@ -518,21 +680,21 @@ loop(St) ->
handle_msg(Msg, St) ->
Module = St#st.module,
case Msg of
- {system, From, Req} ->
- exometer_proc:handle_system_msg(
- Req, From, St, fun(St1) -> loop(St1) end);
+ {system, From, Req} ->
+ exometer_proc:handle_system_msg(
+ Req, From, St, fun(St1) -> loop(St1) end);
{exometer_proc, {From, Ref}, {get_value, default} } ->
{ok, DataPoints} = Module:probe_get_datapoints(St#st.mod_state),
{Reply, NSt} =
- process_probe_reply(St, Module:probe_get_value(DataPoints,
- St#st.mod_state)),
+ process_probe_reply(St, Module:probe_get_value(DataPoints,
+ St#st.mod_state)),
From ! {Ref, Reply },
NSt;
{exometer_proc, {From, Ref}, {get_value, DataPoints} } ->
{Reply, NSt} =
- process_probe_reply(St, Module:probe_get_value(DataPoints,
- St#st.mod_state)),
+ process_probe_reply(St, Module:probe_get_value(DataPoints,
+ St#st.mod_state)),
From ! {Ref, Reply },
NSt;
@@ -556,14 +718,14 @@ handle_msg(Msg, St) ->
{NSt, Opts1} = process_opts(Options, St),
{Reply, NSt1} = %% Call module setopts for remainder of opts
- process_probe_reply(
- NSt, Module:probe_setopts(Entry, Opts1, NSt#st.mod_state)),
+ process_probe_reply(
+ NSt, Module:probe_setopts(Entry, Opts1, NSt#st.mod_state)),
From ! {Ref, Reply },
%% Return state with options and any non-duplicate original opts.
NSt1#st {
opts = Opts1 ++
- [{K,V} || {K,V} <- St#st.opts, not lists:keymember(K,1,Opts1) ]
+ [{K,V} || {K,V} <- St#st.opts, not lists:keymember(K,1,Opts1) ]
};
{timeout, _TRef, {exometer_proc, sample_timer}} ->
@@ -632,3 +794,32 @@ process_opts([Opt|T], St, Acc) ->
process_opts(T, St, [Opt | Acc]);
process_opts([], St, Acc) ->
{St, lists:reverse(Acc)}.
+
+%% EUnit
+
+-ifdef(TEST).
+
+restart_test_() ->
+ [
+ ?_test(t_restart_1()),
+ ?_test(t_restart_2())
+ ].
+
+t_restart_1() ->
+ R = [{{3,1000}, restart}, % up to 3 restarts within 1 sec
+ {{4,'_'} , disable}, % a total of up to 4 restarts
+ {'_', delete}], % anything else
+ OE = on_error_init(R),
+ {restart, OE1} = check_restart(100, OE),
+ {restart, OE2} = check_restart(200, OE1),
+ {restart, OE3} = check_restart(300, OE2),
+ {disable, OE4} = check_restart(400, OE3),
+ {delete , _} = check_restart(500, OE4),
+ ok.
+
+t_restart_2() ->
+ R = [],
+ {delete, _} = check_restart({1, [500], 1000, R}),
+ ok.
+
+-endif.
diff --git a/src/exometer_proc.erl b/src/exometer_proc.erl
index 7e75107..7aa3248 100644
--- a/src/exometer_proc.erl
+++ b/src/exometer_proc.erl
@@ -38,6 +38,7 @@
-module(exometer_proc).
-export([spawn_process/2,
+ spawn_process/3,
cast/2,
call/2,
process_options/1,
@@ -74,9 +75,19 @@ spawn_process(Name, F) when is_function(F,0) ->
Parent = self(),
proc_lib:spawn(fun() ->
exometer_admin:monitor(Name, self()),
- init(Name, Mod, F, Parent)
+ init(Name, Mod, F, Parent)
end).
+spawn_process(Name, F, Opts) ->
+ {_, Mod} = erlang:fun_info(F, module),
+ Parent = self(),
+ SpawnOpts = proplists:get_value(spawn_opts, Opts, []),
+ OnError = proplists:get_value(on_error, Opts, delete),
+ proc_lib:spawn_opt(fun() ->
+ exometer_admin:monitor(Name, self(), OnError),
+ init(Name, Mod, F, Parent)
+ end, SpawnOpts).
+
init(Name, Mod, StartF, ParentPid) ->
I = #info{parent = ParentPid},
Sys = I#info.sys,
@@ -201,8 +212,8 @@ format_status(Opt, StatusData) ->
pid_to_list(Name);
is_atom(Name) ->
Name;
- true ->
- lists:flatten(io_lib:fwrite("~w", [Name]))
+ true ->
+ lists:flatten(io_lib:fwrite("~w", [Name]))
end,
Header = lists:concat(["Status for exometer_proc ", NameTag]),
Log = sys:get_debug(log, Debug, []),
diff --git a/src/exometer_report.erl b/src/exometer_report.erl
index d154bd2..9e5dca3 100644
--- a/src/exometer_report.erl
+++ b/src/exometer_report.erl
@@ -160,6 +160,7 @@
set_interval/3,
delete_interval/2,
restart_intervals/1,
+ trigger_interval/2,
get_intervals/1,
remove_reporter/1, remove_reporter/2,
terminate_reporter/1,
@@ -189,8 +190,8 @@
-type error() :: {error, any()}.
-type metric() :: exometer:name()
- | {find, exometer:name()}
- | {select, ets:match_spec()}.
+ | {find, exometer:name()}
+ | {select, ets:match_spec()}.
-type datapoint() :: exometer:datapoint().
-type datapoints() :: datapoint() | [datapoint()].
-type options() :: [{atom(), any()}].
@@ -200,7 +201,7 @@
-type time_ms() :: pos_integer().
-type delay() :: time_ms().
-type named_interval() :: {atom(), time_ms()}
- | {atom(), time_ms(), delay()}.
+ | {atom(), time_ms(), delay()}.
-type callback_result() :: {ok, mod_state()} | any().
-type extra() :: any().
-type retry() :: boolean().
@@ -216,7 +217,7 @@
-callback exometer_init(options()) -> callback_result().
-callback exometer_report(metric(), datapoint(),
- value(), extra(), mod_state()) ->
+ extra(), value(), mod_state()) ->
callback_result().
-callback exometer_subscribe(metric(), datapoint(),
@@ -240,7 +241,7 @@
any().
-callback exometer_setopts(exometer:entry(), options(),
- exometer:status(), mod_state()) ->
+ exometer:status(), mod_state()) ->
callback_result().
-callback exometer_newentry(exometer:entry(), mod_state()) ->
@@ -267,11 +268,11 @@
).
-record(interval, {
- name :: atom(),
- time = 0 :: non_neg_integer(),
- delay = 0 :: non_neg_integer(),
- t_ref :: reference() | undefined
- }).
+ name :: atom(),
+ time = 0 :: non_neg_integer() | 'manual',
+ delay = 0 :: non_neg_integer(),
+ t_ref :: reference() | undefined
+ }).
-record(reporter, {
name :: atom() | '_',
@@ -279,9 +280,9 @@
mref :: reference() | '_',
module :: module() | '_',
opts = [] :: [{atom(), any()}] | '_',
- intervals = [] :: [#interval{}] | '_',
+ intervals = [] :: [#interval{}] | '_',
restart = #restart{} :: #restart{} | '_',
- status = enabled :: enabled | disabled | '_'
+ status = enabled :: enabled | disabled | '_'
}).
-record(st, {
@@ -302,13 +303,13 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec subscribe(reporter_name(), metric(), datapoints(), interval()) ->
- ok | not_found | unknown_reporter | error.
+ ok | not_found | unknown_reporter | error.
%% @equiv subscribe(Reporter, Metric, DataPoint, Interval, [], false)
subscribe(Reporter, Metric, DataPoint, Interval) ->
subscribe(Reporter, Metric, DataPoint, Interval, []).
-spec subscribe(reporter_name(), metric(), datapoints(), interval(), extra()) ->
- ok | not_found | unknown_reporter | error.
+ ok | not_found | unknown_reporter | error.
%% @equiv subscribe(Reporter, Metric, DataPoint, Interval, Extra, false)
subscribe(Reporter, Metric, DataPoint, Interval, Extra) ->
call({subscribe, #key{reporter = Reporter,
@@ -318,8 +319,8 @@ subscribe(Reporter, Metric, DataPoint, Interval, Extra) ->
extra = Extra}, Interval}).
-spec subscribe(reporter_name(), metric(), datapoints(), interval(),
- extra(), retry()) ->
- ok | not_found | unknown_reporter | error.
+ extra(), retry()) ->
+ ok | not_found | unknown_reporter | error.
%% @doc Add a subscription to an existing reporter.
%%
%% The reporter must first be started using {@link add_reporter/2}, or through
@@ -342,19 +343,19 @@ subscribe(Reporter, Metric, DataPoint, Interval, Extra) ->
subscribe(Reporter, Metric, DataPoint, Interval, Extra, Retry)
when is_boolean(Retry) ->
call({subscribe, #key{reporter = Reporter,
- metric = Metric,
- datapoint = DataPoint,
- retry_failed_metrics = Retry,
- extra = Extra}, Interval}).
+ metric = Metric,
+ datapoint = DataPoint,
+ retry_failed_metrics = Retry,
+ extra = Extra}, Interval}).
-spec unsubscribe(module(), metric(), datapoint()) ->
- ok | not_found.
+ ok | not_found.
%% @equiv unsubscribe(Reporter, Metric, DataPoint, [])
unsubscribe(Reporter, Metric, DataPoint) ->
unsubscribe(Reporter, Metric, DataPoint, []).
-spec unsubscribe(module(), metric(), datapoint() | [datapoint()], extra()) ->
- ok | not_found.
+ ok | not_found.
%% @doc Removes a subscription.
%%
%% Note that the subscription is identified by the combination
@@ -374,18 +375,18 @@ unsubscribe_all(Reporter, Metric) ->
call({unsubscribe_all, Reporter, Metric}).
-spec list_metrics() -> {ok, [{ exometer:name(),
- [datapoint()],
- [{reporter_name(), datapoint()}],
- exometer:status() }]} | {error, any()}.
+ [datapoint()],
+ [{reporter_name(), datapoint()}],
+ exometer:status() }]} | {error, any()}.
%% @equiv list_metrics([])
list_metrics() ->
list_metrics([]).
-spec list_metrics(Path :: metric()) ->
- {ok, [{ exometer:name(),
- [datapoint()],
- [{reporter_name(), datapoint()}],
- exometer:status() }]} | {error, any()}.
+ {ok, [{ exometer:name(),
+ [datapoint()],
+ [{reporter_name(), datapoint()}],
+ exometer:status() }]} | {error, any()}.
%% @doc List all metrics matching `Path', together with subscription status.
%%
%% This function performs a metrics search using `exometer:find_entries/1',
@@ -402,7 +403,7 @@ list_reporters() ->
call(list_reporters).
-spec list_subscriptions(reporter_name()) ->
- [{metric(), datapoint(), interval(), extra()}].
+ [{metric(), datapoint(), interval(), extra()}].
%% @doc List all subscriptions for `Reporter'.
list_subscriptions(Reporter) ->
call({list_subscriptions, Reporter}).
@@ -426,12 +427,15 @@ list_subscriptions(Reporter) ->
%% `{intervals, [named_interval()]}'
%% named_interval() :: {Name::atom(), Interval::pos_integer()}
%% | {Name::atom(), Interval::time_ms(), delay()::time_ms()}
+%% | {Name::atom(), 'manual'}
%% Define named intervals. The name can be used by subscribers, so that all
%% subsriptions for a given named interval will be reported when the interval
%% triggers. An optional delay (in ms) can be given: this will cause the first
%% interval to start in `Delay' milliseconds. When all intervals are named
%% at the same time, the delay parameter can be used to achieve staggered
-%% reporting.
+%% reporting. If the interval is specified as ```'manual'''', it will have
+%% to be triggered manually using {@link trigger_interval/2}.
+%%
%% @end
add_reporter(Reporter, Options) ->
call({add_reporter, Reporter, Options}).
@@ -442,24 +446,28 @@ remove_reporter(Reporter) ->
call({remove_reporter, Reporter}).
-spec set_interval(reporter_name(), atom(),
- time_ms() | {time_ms(), delay()}) -> ok |error().
+ time_ms() | {time_ms(), delay()}) -> ok |error().
%% @doc Specify a named interval.
%%
%% See {@link add_reporter/2} for a description of named intervals.
%% The named interval is here specified as either `Time' (milliseconds) or
-%% `{Time, Delay}', where a delay in milliseconds is provided.
+%% `{Time, Delay}', where a delay in milliseconds is provided. It is also
+%% specify an interval as ```'manual'''', indicating that the interval can
+%% only be triggered manually via {@link trigger_interval/2}.
%%
%% If the named interval exists, it will be replaced with the new definition.
%% Otherwise, it will be added. Use {@link restart_intervals/1} if you want
%% all intervals to be restarted/resynched with corresponding relative delays.
%% @end
set_interval(Reporter, Name, Time) when is_atom(Name),
- is_integer(Time), Time >= 0 ->
+ is_integer(Time), Time >= 0 ->
call({set_interval, Reporter, Name, Time});
+set_interval(Reporter, Name, manual) when is_atom(Name) ->
+ call({set_interval, Reporter, Name, manual});
set_interval(Reporter, Name, {Time, Delay}) when is_atom(Name),
- is_integer(Time), Time >= 0,
- is_integer(Delay),
- Delay >= 0 ->
+ is_integer(Time), Time >= 0,
+ is_integer(Delay),
+ Delay >= 0 ->
call({set_interval, Reporter, Name, {Time, Delay}}).
-spec delete_interval(reporter_name(), atom()) -> ok | error().
@@ -477,10 +485,21 @@ delete_interval(Reporter, Name) ->
restart_intervals(Reporter) ->
call({restart_intervals, Reporter}).
+-spec trigger_interval(reporter_name(), atom()) -> ok.
+%% @doc Trigger a named interval.
+%%
+%% This function is mainly used to trigger intervals defined as ```'manual'''',
+%% but can be used to trigger any named interval. If a named interval with
+%% a specified time in milliseconds is triggered this way, it will effectively
+%% be restarted, and will repeat as usual from that point on.
+%% @end
+trigger_interval(Reporter, Name) ->
+ cast({trigger_interval, Reporter, Name}).
+
-spec get_intervals(reporter_name()) ->
- [{atom(), [{time, pos_integer()}
- | {delay, pos_integer()}
- | {timer_ref, reference()}]}].
+ [{atom(), [{time, pos_integer()}
+ | {delay, pos_integer()}
+ | {timer_ref, reference()}]}].
%% @doc List the named intervals for `Reporter'.
get_intervals(Reporter) ->
call({get_intervals, Reporter}).
@@ -518,9 +537,9 @@ disable_reporter(Reporter) ->
disable_me(Mod, St) ->
cast({disable, self()}),
receive
- {exometer_terminate, shutdown} ->
- Mod:exometer_terminate(shutdown, St),
- exit(shutdown)
+ {exometer_terminate, shutdown} ->
+ Mod:exometer_terminate(shutdown, St),
+ exit(shutdown)
end.
-spec call_reporter(reporter_name(), any()) -> any() | {error, any()}.
@@ -531,10 +550,10 @@ disable_me(Mod, St) ->
%% @end
call_reporter(Reporter, Msg) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{pid = Pid}] when is_pid(Pid) ->
+ [#reporter{pid = Pid}] when is_pid(Pid) ->
exometer_proc:call(Pid, Msg);
- [#reporter{status = disabled}] ->
- {error, disabled};
+ [#reporter{status = disabled}] ->
+ {error, disabled};
[] ->
{error, {no_such_reporter, Reporter}}
end.
@@ -547,10 +566,10 @@ call_reporter(Reporter, Msg) ->
%% @end
cast_reporter(Reporter, Msg) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{pid = Pid}] when is_pid(Pid) ->
+ [#reporter{pid = Pid}] when is_pid(Pid) ->
exometer_proc:cast(Pid, Msg);
- [#reporter{status = disabled}] ->
- {error, disabled};
+ [#reporter{status = disabled}] ->
+ {error, disabled};
[] ->
{error, {no_such_reporter, Reporter}}
end.
@@ -602,7 +621,7 @@ new_entry(Entry) ->
%% @end
%%--------------------------------------------------------------------
init([]) ->
- process_flag(trap_exit, true),
+ process_flag(trap_exit, true),
{ok, #st{}}.
start_reporters() ->
@@ -616,45 +635,45 @@ do_start_reporters(S) ->
%% Traverse list of reporter and launch reporter gen servers as dynamic
%% supervisor children.
case lists:keyfind(reporters, 1, Opts) of
- {reporters, ReporterList} ->
- ReporterRecs = make_reporter_recs(ReporterList),
- assert_no_duplicates(ReporterRecs),
- lists:foreach(
- fun(#reporter{name = Reporter,
- status = Status,
- opts = ROpts,
- intervals = Ints0} = R) ->
- Restart = get_restart(ROpts),
- {Pid, MRef, Ints} =
- if Status =:= enabled ->
- {P1,R1} = spawn_reporter(Reporter, ROpts),
- I1 = start_interval_timers(R),
- {P1,R1,I1};
- true -> {undefined, undefined, Ints0}
- end,
- ets:insert(?EXOMETER_REPORTERS,
- R#reporter{pid = Pid,
- mref = MRef,
- intervals = Ints,
- restart = Restart})
- end, ReporterRecs);
- false ->
- []
+ {reporters, ReporterList} ->
+ ReporterRecs = make_reporter_recs(ReporterList),
+ assert_no_duplicates(ReporterRecs),
+ lists:foreach(
+ fun(#reporter{name = Reporter,
+ status = Status,
+ opts = ROpts,
+ intervals = Ints0} = R) ->
+ Restart = get_restart(ROpts),
+ {Pid, MRef, Ints} =
+ if Status =:= enabled ->
+ {P1,R1} = spawn_reporter(Reporter, ROpts),
+ I1 = start_interval_timers(R),
+ {P1,R1,I1};
+ true -> {undefined, undefined, Ints0}
+ end,
+ ets:insert(?EXOMETER_REPORTERS,
+ R#reporter{pid = Pid,
+ mref = MRef,
+ intervals = Ints,
+ restart = Restart})
+ end, ReporterRecs);
+ false ->
+ []
end,
%% Dig out configured 'static' subscribers
case lists:keyfind(subscribers, 1, Opts) of
- {subscribers, Subscribers} ->
- lists:foreach(fun init_subscriber/1, Subscribers);
- false -> []
+ {subscribers, Subscribers} ->
+ lists:foreach(fun init_subscriber/1, Subscribers);
+ false -> []
end,
S#st{}.
make_reporter_recs([{R, Opts}|T]) when is_atom(R), is_list(Opts) ->
[#reporter{name = R,
module = get_module(R, Opts),
- status = proplists:get_value(status, Opts, enabled),
+ status = proplists:get_value(status, Opts, enabled),
opts = Opts,
- intervals = get_interval_opts(Opts)}|make_reporter_recs(T)];
+ intervals = get_interval_opts(Opts)}|make_reporter_recs(T)];
make_reporter_recs([]) ->
[].
@@ -667,41 +686,54 @@ get_interval_opts(Opts) ->
Is = proplists:get_value(intervals, Opts, []),
lists:map(
fun({Name, Time}) when is_atom(Name),
- is_integer(Time), Time >= 0 ->
- #interval{name = Name, time = Time};
- ({Name, Time, Delay}) when is_atom(Name),
- is_integer(Time), Time >= 0,
- is_integer(Delay), Delay >= 0 ->
- #interval{name = Name, time = Time, delay = Delay};
- (Other) ->
- error({invalid_interval, Other})
+ is_integer(Time), Time >= 0 ->
+ #interval{name = Name, time = Time};
+ ({Name, Time, Delay}) when is_atom(Name),
+ is_integer(Time), Time >= 0,
+ is_integer(Delay), Delay >= 0 ->
+ #interval{name = Name, time = Time, delay = Delay};
+ ({Name, manual}) when is_atom(Name) ->
+ #interval{name = Name, time = manual};
+ (Other) ->
+ error({invalid_interval, Other})
end, Is ++ Is1).
singelton_interval({N,T}=I) when is_atom(N), is_integer(T) -> I;
singelton_interval({N,T,D}=I) when is_atom(N),
- is_integer(T),
- is_integer(D) -> I.
+ is_integer(T),
+ is_integer(D) -> I.
start_interval_timers(#reporter{name = R, intervals = Ints}) ->
lists:map(fun(I) -> start_interval_timer(I, R) end, Ints).
+start_interval_timer(#interval{time = manual} = I, _) ->
+ I;
start_interval_timer(#interval{name = Name, delay = Delay,
- t_ref = Ref} = I, R) ->
+ t_ref = Ref} = I, R) ->
cancel_timer(Ref),
case Delay of
- 0 ->
- do_start_interval_timer(I, R);
- D ->
- TRef = erlang:send_after(D, self(), {start_interval, R, Name}),
- I#interval{t_ref = TRef}
+ 0 ->
+ do_start_interval_timer(I, R);
+ D ->
+ TRef = erlang:send_after(D, self(), {start_interval, R, Name}),
+ I#interval{t_ref = TRef}
end.
do_start_interval_timer(#interval{name = Name, time = Time} = I, R) ->
- TRef = erlang:send_after(Time, self(), batch_timer_msg(R, Name)),
+ TRef = erlang:send_after(Time, self(), batch_timer_msg(R, Name, Time)),
I#interval{t_ref = TRef}.
-batch_timer_msg(R, Name) ->
- {report_batch, R, Name}.
+batch_timer_msg(R, Name, Time) ->
+ batch_timer_msg(R, Name, Time, os:timestamp()).
+
+batch_timer_msg(R, Name, Time, TS) ->
+ {report_batch, R, Name, Time, TS}.
+
+subscr_timer_msg(Key, Interval) ->
+ subscr_timer_msg(Key, Interval, os:timestamp()).
+
+subscr_timer_msg(Key, Interval, TS) ->
+ {report, Key, Interval, TS}.
get_report_env() ->
Opts0 = exometer_util:get_env(report, []),
@@ -756,29 +788,29 @@ handle_call({subscribe,
%% Verify that the given metric/data point actually exist.
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{status = Status}] ->
- case is_valid_metric(Metric, DataPoint) of
- true ->
- if Status =:= enabled ->
- Reporter ! {exometer_subscribe, Metric,
- DataPoint, Interval, Extra};
- true -> ignore
- end,
+ [#reporter{status = Status}] ->
+ case is_valid_metric(Metric, DataPoint) of
+ true ->
+ if Status =:= enabled ->
+ Reporter ! {exometer_subscribe, Metric,
+ DataPoint, Interval, Extra};
+ true -> ignore
+ end,
subscribe_(Reporter, Metric, DataPoint,
- Interval, RetryFailedMetrics,
- Extra, Status),
+ Interval, RetryFailedMetrics,
+ Extra, Status),
{reply, ok, St};
%% Nope - Not found.
- false ->
- case RetryFailedMetrics of
- true ->
- subscribe_(Reporter, Metric, DataPoint,
- Interval, RetryFailedMetrics,
- Extra, Status),
- {reply, ok, St};
- false ->
- {reply, not_found, St}
- end;
+ false ->
+ case RetryFailedMetrics of
+ true ->
+ subscribe_(Reporter, Metric, DataPoint,
+ Interval, RetryFailedMetrics,
+ Extra, Status),
+ {reply, ok, St};
+ false ->
+ {reply, not_found, St}
+ end;
error -> {reply, error, St}
end;
[] ->
@@ -796,43 +828,43 @@ handle_call({unsubscribe,
handle_call({unsubscribe_all, Reporter, Metric}, _,
#st{}=St) ->
Subs = ets:select(?EXOMETER_SUBS,
- [{#subscriber{key = #key{reporter = Reporter,
- metric = Metric,
- _ = '_'},
- _ = '_'}, [], ['$_']}]),
+ [{#subscriber{key = #key{reporter = Reporter,
+ metric = Metric,
+ _ = '_'},
+ _ = '_'}, [], ['$_']}]),
lists:foreach(fun unsubscribe_/1, Subs),
{reply, ok, St};
handle_call({list_metrics, Path}, _, St) ->
if is_list(Path) ->
- DP = lists:foldr(fun(Metric, Acc) ->
- retrieve_metric(Metric, Acc)
- end, [], exometer:find_entries(Path)),
- {reply, {ok, DP}, St};
+ DP = lists:foldr(fun(Metric, Acc) ->
+ retrieve_metric(Metric, Acc)
+ end, [], exometer:find_entries(Path)),
+ {reply, {ok, DP}, St};
true ->
- {reply, {error, badarg}, St}
+ {reply, {error, badarg}, St}
end;
handle_call({list_subscriptions, Reporter}, _, #st{} = St) ->
Subs1 = lists:foldl(
fun
(#subscriber{key=#key{reporter=Rep}}=Sub, Acc) when Reporter == Rep ->
- #subscriber{
- key=#key{
- metric=Metric,
- datapoint=Dp,
- extra=Extra},
- interval=Interval} = Sub,
- [{Metric, Dp, Interval, Extra} | Acc];
+ #subscriber{
+ key=#key{
+ metric=Metric,
+ datapoint=Dp,
+ extra=Extra},
+ interval=Interval} = Sub,
+ [{Metric, Dp, Interval, Extra} | Acc];
(_, Acc) ->
- Acc
- end, [], ets:select(?EXOMETER_SUBS, [{'_',[],['$_']}])),
+ Acc
+ end, [], ets:select(?EXOMETER_SUBS, [{'_',[],['$_']}])),
{reply, Subs1, St};
handle_call(list_reporters, _, #st{} = St) ->
Info = ets:select(?EXOMETER_REPORTERS,
- [{#reporter{name = '$1', pid = '$2', _ = '_'},
- [], [{{'$1', '$2'}}]}]),
+ [{#reporter{name = '$1', pid = '$2', _ = '_'},
+ [], [{{'$1', '$2'}}]}]),
{reply, Info, St};
handle_call({add_reporter, Reporter, Opts}, _, #st{} = St) ->
@@ -840,24 +872,24 @@ handle_call({add_reporter, Reporter, Opts}, _, #st{} = St) ->
true ->
{reply, {error, already_running}, St};
false ->
- try
- [R] = make_reporter_recs([{Reporter, Opts}]),
- {Pid, MRef} = spawn_reporter(Reporter, Opts),
- Ints = start_interval_timers(R),
- R1 = R#reporter{intervals = Ints,
- pid = Pid,
- mref = MRef},
- ets:insert(?EXOMETER_REPORTERS, R1),
- {reply, ok, St}
- catch
- error:Reason ->
- {reply, {error, Reason}, St}
- end
+ try
+ [R] = make_reporter_recs([{Reporter, Opts}]),
+ {Pid, MRef} = spawn_reporter(Reporter, Opts),
+ Ints = start_interval_timers(R),
+ R1 = R#reporter{intervals = Ints,
+ pid = Pid,
+ mref = MRef},
+ ets:insert(?EXOMETER_REPORTERS, R1),
+ {reply, ok, St}
+ catch
+ error:Reason ->
+ {reply, {error, Reason}, St}
+ end
end;
handle_call({remove_reporter, Reporter}, _, St) ->
case do_remove_reporter(Reporter) of
- ok ->
+ ok ->
{reply, ok, St};
E ->
{reply, E, St}
@@ -865,79 +897,82 @@ handle_call({remove_reporter, Reporter}, _, St) ->
handle_call({change_reporter_status, Reporter, Status}, _, St) ->
case change_reporter_status(Reporter, Status) of
- ok ->
- {reply, ok, St};
- E ->
- {reply, E, St}
+ ok ->
+ {reply, ok, St};
+ E ->
+ {reply, E, St}
end;
handle_call({set_interval, Reporter, Name, Int}, _, #st{}=St) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{intervals = Ints}] ->
- try
- I0 = case lists:keyfind(Name, #interval.name, Ints) of
- false -> #interval{name = Name};
- Interval -> Interval
- end,
- I1 = case Int of
- {Time, Delay} when is_integer(Time), Time >= 0,
- is_integer(Delay), Delay >= 0 ->
- I0#interval{time = Time, delay = Delay};
- Time when is_integer(Time), Time >= 0 ->
- I0#interval{time = Time}
- end,
- ets:update_element(?EXOMETER_REPORTERS, Reporter,
- [{#reporter.intervals,
- lists:keystore(
- Name, #interval.name, Ints,
- start_interval_timer(I1, Reporter))}]),
- {reply, ok, St}
- catch
- error:Reason ->
- {reply, {error, Reason}, St}
- end;
- [] ->
- {reply, {error, not_found}, St}
+ [#reporter{intervals = Ints}] ->
+ try
+ I0 = case lists:keyfind(Name, #interval.name, Ints) of
+ false -> #interval{name = Name};
+ Interval -> Interval
+ end,
+ I1 = case Int of
+ {Time, Delay} when is_integer(Time), Time >= 0,
+ is_integer(Delay), Delay >= 0 ->
+ I0#interval{time = Time, delay = Delay};
+ Time when is_integer(Time), Time >= 0 ->
+ I0#interval{time = Time};
+ manual ->
+ cancel_timer(I0#interval.t_ref),
+ I0#interval{time = manual}
+ end,
+ ets:update_element(?EXOMETER_REPORTERS, Reporter,
+ [{#reporter.intervals,
+ lists:keystore(
+ Name, #interval.name, Ints,
+ start_interval_timer(I1, Reporter))}]),
+ {reply, ok, St}
+ catch
+ error:Reason ->
+ {reply, {error, Reason}, St}
+ end;
+ [] ->
+ {reply, {error, not_found}, St}
end;
handle_call({delete_interval, Reporter, Name}, _, #st{} = St) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{intervals = Ints}] ->
- case lists:keyfind(Name, #interval.name, Ints) of
- #interval{t_ref = TRef} ->
- cancel_timer(TRef),
- ets:update_element(?EXOMETER_REPORTERS, Reporter,
- [{#reporter.intervals,
- lists:keydelete(
- Name, #interval.name, Ints)}]),
- {reply, ok, St};
- false ->
- {reply, {error, not_found}, St}
- end;
- [] ->
- {reply, {error, not_found}, St}
+ [#reporter{intervals = Ints}] ->
+ case lists:keyfind(Name, #interval.name, Ints) of
+ #interval{t_ref = TRef} ->
+ cancel_timer(TRef),
+ ets:update_element(?EXOMETER_REPORTERS, Reporter,
+ [{#reporter.intervals,
+ lists:keydelete(
+ Name, #interval.name, Ints)}]),
+ {reply, ok, St};
+ false ->
+ {reply, {error, not_found}, St}
+ end;
+ [] ->
+ {reply, {error, not_found}, St}
end;
handle_call({restart_intervals, Reporter}, _, #st{} = St) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{} = R] ->
- Ints = start_interval_timers(R),
- ets:update_element(?EXOMETER_REPORTERS, Reporter,
- [{#reporter.intervals, Ints}]),
- {reply, ok, St};
- [] ->
- {reply, {error, not_found}, St}
+ [#reporter{} = R] ->
+ Ints = start_interval_timers(R),
+ ets:update_element(?EXOMETER_REPORTERS, Reporter,
+ [{#reporter.intervals, Ints}]),
+ {reply, ok, St};
+ [] ->
+ {reply, {error, not_found}, St}
end;
handle_call({get_intervals, Reporter}, _, #st{} = St) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{intervals = Ints}] ->
- Info =
- [{Name, [{time, T},
- {delay, D},
- {timer_ref, TR}]} || #interval{name = Name,
- time = T,
- delay = D,
- t_ref = TR} <- Ints],
- {reply, Info, St};
- [] ->
- {reply, {error, not_found}, St}
+ [#reporter{intervals = Ints}] ->
+ Info =
+ [{Name, [{time, T},
+ {delay, D},
+ {timer_ref, TR}]} || #interval{name = Name,
+ time = T,
+ delay = D,
+ t_ref = TR} <- Ints],
+ {reply, Info, St};
+ [] ->
+ {reply, {error, not_found}, St}
end;
handle_call({setopts, Metric, Options, Status}, _, #st{}=St) ->
[erlang:send(Pid, {exometer_setopts, Metric, Options, Status})
@@ -974,11 +1009,14 @@ handle_cast({remove_reporter, Reporter, Reason}, St) ->
{noreply, St};
handle_cast({disable, Pid}, #st{} = St) ->
case reporter_by_pid(Pid) of
- [#reporter{} = Reporter] ->
- do_change_reporter_status(Reporter, disabled);
- [] -> ok
+ [#reporter{} = Reporter] ->
+ do_change_reporter_status(Reporter, disabled);
+ [] -> ok
end,
{noreply, St};
+handle_cast({trigger_interval, Reporter, Name}, #st{} = St) ->
+ report_batch(Reporter, Name, os:timestamp()),
+ {noreply, St};
handle_cast(_Msg, State) ->
{noreply, State}.
@@ -993,57 +1031,57 @@ handle_cast(_Msg, State) ->
%%--------------------------------------------------------------------
handle_info({start_interval, Reporter, Name}, #st{} = St) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{intervals = Ints, status = enabled}] ->
- case lists:keyfind(Name, #interval.name, Ints) of
- #interval{} = I ->
- I1 = do_start_interval_timer(I, Reporter),
- ets:update_element(?EXOMETER_REPORTERS, Reporter,
- [{#reporter.intervals,
- lists:keyreplace(
- Name, #interval.name, Ints, I1)}]);
- false ->
- ok
- end;
- _ ->
- ok
+ [#reporter{intervals = Ints, status = enabled}] ->
+ case lists:keyfind(Name, #interval.name, Ints) of
+ #interval{time = Time} = I when is_integer(Time) ->
+ I1 = do_start_interval_timer(I, Reporter),
+ ets:update_element(?EXOMETER_REPORTERS, Reporter,
+ [{#reporter.intervals,
+ lists:keyreplace(
+ Name, #interval.name, Ints, I1)}]);
+ #interval{time = manual} ->
+ ok;
+ false ->
+ ok
+ end;
+ _ ->
+ ok
end,
{noreply, St};
handle_info({report_batch, Reporter, Name}, #st{} = St) ->
%% Find all entries where reporter is Reporter and interval is Name,
%% and report them.
- report_batch(Reporter, Name),
+ report_batch(Reporter, Name, os:timestamp()),
{noreply, St};
-handle_info({report, #key{reporter = Reporter} = Key, Interval}, #st{} = St) ->
- case ets:member(?EXOMETER_SUBS, Key) andalso
- get_reporter_status(Reporter) == enabled of
- true ->
- case do_report(Key, Interval) of
- true -> restart_subscr_timer(Key, Interval);
- false -> ok
- end,
- {noreply, St};
- false ->
- %% Possibly an unsubscribe removed the subscriber
- ?error("No such subscriber (Key=~p)~n", [Key]),
- {noreply, St}
- end;
+handle_info({report_batch, Reporter, Name, Int, TS}, #st{} = St) ->
+ %% Find all entries where reporter is Reporter and interval is Name,
+ %% and report them.
+ TS1 = calc_fire_time(TS, Int),
+ report_batch(Reporter, Name, TS1),
+ {noreply, St};
+handle_info({report, #key{} = Key, Interval}, #st{} = St) ->
+ %% BW Compat. Old-style timeout msg, which doesn't include timestamp
+ {noreply, handle_report(Key, Interval, os:timestamp(), St)};
+handle_info({report, #key{} = Key, Interval, TS}, #st{} = St) ->
+ TS1 = calc_fire_time(TS, Interval),
+ {noreply, handle_report(Key, Interval, TS1, St)};
handle_info({'DOWN', Ref, process, _Pid, Reason}, #st{} = S) ->
case reporter_by_mref(Ref) of
- [#reporter{module = Module, restart = Restart} = R] ->
- case add_restart(Restart) of
- {remove, How} ->
- case How of
- {M, F} when is_atom(M), is_atom(F) ->
- try M:F(Module, Reason) catch _:_ -> ok end;
- _ ->
- ok
- end,
- S;
- {restart, Restart1} ->
- restart_reporter(R#reporter{restart = Restart1})
- end;
- _ -> S
+ [#reporter{module = Module, restart = Restart} = R] ->
+ case add_restart(Restart) of
+ {remove, How} ->
+ case How of
+ {M, F} when is_atom(M), is_atom(F) ->
+ try M:F(Module, Reason) catch _:_ -> ok end;
+ _ ->
+ ok
+ end,
+ S;
+ {restart, Restart1} ->
+ restart_reporter(R#reporter{restart = Restart1})
+ end;
+ _ -> S
end,
{noreply, S};
@@ -1054,123 +1092,158 @@ handle_info(_Info, State) ->
restart_reporter(#reporter{name = Name, opts = Opts, restart = Restart}) ->
{Pid, MRef} = spawn_reporter(Name, Opts),
[resubscribe(S) ||
- S <- ets:select(?EXOMETER_SUBS,
- [{#subscriber{key = #key{reporter = Name,
- _ = '_'},
- _ = '_'}, [], ['$_']}])],
+ S <- ets:select(?EXOMETER_SUBS,
+ [{#subscriber{key = #key{reporter = Name,
+ _ = '_'},
+ _ = '_'}, [], ['$_']}])],
ets:update_element(?EXOMETER_REPORTERS, Name,
- [{#reporter.pid, Pid},
- {#reporter.mref, MRef},
- {#reporter.restart, Restart},
- {#reporter.status, enabled}]),
+ [{#reporter.pid, Pid},
+ {#reporter.mref, MRef},
+ {#reporter.restart, Restart},
+ {#reporter.status, enabled}]),
ok.
%% If there are already subscriptions, enable them.
maybe_enable_subscriptions(#exometer_entry{name = Metric}) ->
lists:foreach(
fun(#subscriber{key = #key{reporter = RName}} = S) ->
- case get_reporter_status(RName) of
- enabled ->
- resubscribe(S);
- _ ->
- ok
- end
+ case get_reporter_status(RName) of
+ enabled ->
+ resubscribe(S);
+ _ ->
+ ok
+ end
end, ets:select(?EXOMETER_SUBS,
- [{#subscriber{key = #key{metric = Metric,
- _ = '_'},
- _ = '_'}, [], ['$_']}])).
+ [{#subscriber{key = #key{metric = Metric,
+ _ = '_'},
+ _ = '_'}, [], ['$_']}])).
resubscribe(#subscriber{key = #key{reporter = RName,
- metric = Metric,
- datapoint = DataPoint,
- extra = Extra} = Key,
- t_ref = OldTRef,
- interval = Interval}) ->
+ metric = Metric,
+ datapoint = DataPoint,
+ extra = Extra} = Key,
+ t_ref = OldTRef,
+ interval = Interval}) when is_integer(Interval) ->
try_send(RName, {exometer_subscribe, Metric, DataPoint, Interval, Extra}),
cancel_timer(OldTRef),
- TRef = erlang:send_after(Interval, self(), {report, Key, Interval}),
- ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]).
-
+ TRef = erlang:send_after(Interval, self(),
+ subscr_timer_msg(Key, Interval)),
+ ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]);
+
+resubscribe(_) -> undefined.
+
+handle_report(#key{reporter = Reporter} = Key, Interval, TS, #st{} = St) ->
+ _ = case ets:member(?EXOMETER_SUBS, Key) andalso
+ get_reporter_status(Reporter) == enabled of
+ true ->
+ case do_report(Key, Interval) of
+ true -> restart_subscr_timer(Key, Interval, TS);
+ false -> ok
+ end;
+ false ->
+ %% Possibly an unsubscribe removed the subscriber
+ ?error("No such subscriber (Key=~p)~n", [Key])
+ end,
+ St.
do_report(#key{metric = Metric,
- datapoint = DataPoint,
- retry_failed_metrics = RetryFailedMetrics} = Key, Interval) ->
+ datapoint = DataPoint,
+ retry_failed_metrics = RetryFailedMetrics} = Key, Interval) ->
case {RetryFailedMetrics, get_values(Metric, DataPoint)} of
- %% We found a value, or values.
- {_, [_|_] = Found} ->
- %% Distribute metric value to the correct process
- report_values(Found, Key),
- true;
- %% We did not find a value, but we should try again.
- {true, _ } ->
- if is_list(Metric) ->
- ?debug("Metric(~p) Datapoint(~p) not found."
- " Will try again in ~p msec~n",
- [Metric, DataPoint, Interval]),
- true;
- true -> false
- end;
- %% We did not find a value, and we should not retry.
- _ ->
- %% Entry removed while timer in progress.
- ?warning("Metric(~p) Datapoint(~p) not found. Will not try again~n",
- [Metric, DataPoint]),
- false
+ %% We found a value, or values.
+ {_, [_|_] = Found} ->
+ %% Distribute metric value to the correct process
+ report_values(Found, Key),
+ true;
+ %% We did not find a value, but we should try again.
+ {true, _ } ->
+ if is_list(Metric) ->
+ ?debug("Metric(~p) Datapoint(~p) not found."
+ " Will try again in ~p msec~n",
+ [Metric, DataPoint, Interval]),
+ true;
+ true -> false
+ end;
+ %% We did not find a value, and we should not retry.
+ _ ->
+ %% Entry removed while timer in progress.
+ ?warning("Metric(~p) Datapoint(~p) not found. Will not try again~n",
+ [Metric, DataPoint]),
+ false
end.
-report_batch(Reporter, Name) when is_atom(Name) ->
+report_batch(Reporter, Name, T0) when is_atom(Name) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{status = disabled}] ->
- false;
- [R] ->
- Entries = ets:select(?EXOMETER_SUBS,
- [{#subscriber{key = #key{reporter = Reporter,
- _ = '_'},
- interval = Name,
- _ = '_'}, [], ['$_']}]),
- lists:foreach(
- fun(#subscriber{key = Key}) ->
- do_report(Key, Name)
- end, Entries),
- restart_batch_timer(Name, R);
- [] ->
- false
+ [#reporter{status = disabled}] ->
+ false;
+ [R] ->
+ Entries = ets:select(?EXOMETER_SUBS,
+ [{#subscriber{key = #key{reporter = Reporter,
+ _ = '_'},
+ interval = Name,
+ _ = '_'}, [], ['$_']}]),
+ lists:foreach(
+ fun(#subscriber{key = Key}) ->
+ do_report(Key, Name)
+ end, Entries),
+ restart_batch_timer(Name, R, T0);
+ [] ->
+ false
end.
cancel_subscr_timers(Reporter) ->
lists:foreach(
fun(#subscriber{key = Key, t_ref = TRef}) ->
- cancel_timer(TRef),
- ets:update_element(?EXOMETER_SUBS, Key,
- [{#subscriber.t_ref, undefined}])
+ cancel_timer(TRef),
+ ets:update_element(?EXOMETER_SUBS, Key,
+ [{#subscriber.t_ref, undefined}])
end, ets:select(?EXOMETER_SUBS,
- [{#subscriber{key = #key{reporter = Reporter,
- _ = '_'},
- _ = '_'}, [], ['$_']}])).
+ [{#subscriber{key = #key{reporter = Reporter,
+ _ = '_'},
+ _ = '_'}, [], ['$_']}])).
-restart_subscr_timer(Key, Interval) when is_integer(Interval) ->
- TRef = erlang:send_after(Interval, self(),
- {report, Key, Interval}),
+restart_subscr_timer(Key, Interval, T0) when is_integer(Interval) ->
+ TRef = erlang:send_after(adjust_interval(Interval, T0), self(),
+ subscr_timer_msg(Key, Interval, T0)),
ets:update_element(?EXOMETER_SUBS, Key,
- [{#subscriber.t_ref, TRef}]);
-restart_subscr_timer(_, _) ->
+ [{#subscriber.t_ref, TRef}]);
+restart_subscr_timer(_, _, _) ->
true.
restart_batch_timer(Name, #reporter{name = Reporter,
- intervals = Ints}) when is_list(Ints) ->
+ intervals = Ints}, T0) when is_list(Ints) ->
case lists:keyfind(Name, #interval.name, Ints) of
- #interval{time = Time} = I ->
- TRef = erlang:send_after(Time, self(),
- batch_timer_msg(Reporter, Name)),
- ets:update_element(?EXOMETER_REPORTERS, Reporter,
- [{#reporter.intervals,
- lists:keyreplace(Name, #interval.name, Ints,
- I#interval{t_ref = TRef})}]);
- false ->
- false
+ #interval{time = Time, t_ref = OldTRef} = I when is_integer(Time) ->
+ cancel_timer(OldTRef),
+ TRef = erlang:send_after(
+ adjust_interval(Time, T0), self(),
+ batch_timer_msg(Reporter, Name, Time, T0)),
+ ets:update_element(?EXOMETER_REPORTERS, Reporter,
+ [{#reporter.intervals,
+ lists:keyreplace(Name, #interval.name, Ints,
+ I#interval{t_ref = TRef})}]);
+ #interval{time = manual} ->
+ false;
+ false ->
+ false
end.
+adjust_interval(Time, T0) ->
+ T1 = os:timestamp(),
+ erlang:max(0, Time - tdiff(T1, T0)).
+
+tdiff(T1, T0) ->
+ timer:now_diff(T1, T0) div 1000.
+
+%% Calculate time when timer should have fired, based on timestamp logged
+%% at send_after/3 and the intended interval (in ms).
+calc_fire_time({manual, TS}, _) ->
+ TS;
+calc_fire_time({M,S,U}, Int) ->
+ {M, S, U + (Int*1000)}.
+
+
cancel_timer(undefined) ->
false;
cancel_timer(TRef) ->
@@ -1215,14 +1288,14 @@ code_change(_OldVan, #st{reporters = Rs, subscribers = Ss} = S, _Extra) ->
#reporter{name = Module, pid = Pid, mref = MRef,
module = Module, opts = Opts,
restart = Restart};
- ({reporter,Name,Pid,MRef,Module,Opts,Restart}) ->
- #reporter{name = Name, pid = Pid, mref = MRef,
- module = Module, opts = Opts,
- restart = Restart};
- ({reporter,Name,Pid,Mref,Module,Opts,Restart,Status}) ->
- #reporter{name = Name, pid = Pid, mref = Mref,
- module = Module, opts = Opts,
- restart = Restart, status = Status};
+ ({reporter,Name,Pid,MRef,Module,Opts,Restart}) ->
+ #reporter{name = Name, pid = Pid, mref = MRef,
+ module = Module, opts = Opts,
+ restart = Restart};
+ ({reporter,Name,Pid,Mref,Module,Opts,Restart,Status}) ->
+ #reporter{name = Name, pid = Pid, mref = Mref,
+ module = Module, opts = Opts,
+ restart = Restart, status = Status};
(#reporter{} = R) -> R
end, Rs),
[ets:insert(?EXOMETER_REPORTERS, R) || R <- Rs1],
@@ -1237,22 +1310,22 @@ code_change(_OldVsn, State, _Extra) ->
reporter_pids() ->
ets:select(?EXOMETER_REPORTERS,
- [{#reporter{pid = '$1', _ = '_'},
- [{is_pid,'$1'}], ['$1']}]).
+ [{#reporter{pid = '$1', _ = '_'},
+ [{is_pid,'$1'}], ['$1']}]).
reporter_by_pid(Pid) ->
ets:select(?EXOMETER_REPORTERS,
- [{#reporter{pid = Pid, _='_'}, [], ['$_']}]).
+ [{#reporter{pid = Pid, _='_'}, [], ['$_']}]).
reporter_by_mref(Ref) ->
ets:select(?EXOMETER_REPORTERS,
- [{#reporter{mref = Ref, _='_'}, [], ['$_']}]).
+ [{#reporter{mref = Ref, _='_'}, [], ['$_']}]).
try_send(To, Msg) ->
try To ! Msg
catch
- error:_ ->
- Msg
+ error:_ ->
+ Msg
end.
is_valid_metric({find, Name}, _DataPoint) when is_list(Name) ->
@@ -1260,25 +1333,25 @@ is_valid_metric({find, Name}, _DataPoint) when is_list(Name) ->
is_valid_metric({select, Name}, _DataPoint) when is_list(Name) ->
try ets:match_spec_compile(Name), true
catch
- error:_ -> error
+ error:_ -> error
end;
is_valid_metric(Name, default) when is_list(Name) ->
case exometer:info(Name, type) of
- undefined -> false;
- _ -> true
+ undefined -> false;
+ _ -> true
end;
is_valid_metric(Name, DataPoint) when is_list(Name) ->
case dp_list(DataPoint) of
- [] -> error;
- [_|_] = DataPoints ->
- case exometer:info(Name, datapoints) of
- undefined -> false;
- DPs ->
- case DataPoints -- DPs of
- [] -> true;
- _ -> false
- end
- end
+ [] -> error;
+ [_|_] = DataPoints ->
+ case exometer:info(Name, datapoints) of
+ undefined -> false;
+ DPs ->
+ case DataPoints -- DPs of
+ [] -> true;
+ _ -> false
+ end
+ end
end;
is_valid_metric(_, _) ->
false.
@@ -1294,25 +1367,25 @@ dp_list(999) -> [999].
get_values(Name, DataPoint) when is_list(Name) ->
case exometer:get_value(Name, DataPoint) of
- {ok, Values} when is_list(Values) ->
- [{Name, Values}];
- _ ->
- []
+ {ok, Values} when is_list(Values) ->
+ [{Name, Values}];
+ _ ->
+ []
end;
get_values({How, Path}, DataPoint) ->
Entries = case How of
- find -> exometer:find_entries(Path);
- select -> exometer:select(Path)
- end,
+ find -> exometer:find_entries(Path);
+ select -> exometer:select(Path)
+ end,
lists:foldr(
fun({Name, _, enabled}, Acc) ->
- case exometer:get_value(Name, DataPoint) of
- {ok, Values} when is_list(Values) ->
- [{Name, Values}|Acc];
- _ ->
- Acc
- end;
- (_, Acc) -> Acc
+ case exometer:get_value(Name, DataPoint) of
+ {ok, Values} when is_list(Values) ->
+ [{Name, Values}|Acc];
+ _ ->
+ Acc
+ end;
+ (_, Acc) -> Acc
end, [], Entries).
@@ -1329,7 +1402,7 @@ spawn_reporter(Reporter, Opt) when is_atom(Reporter), is_list(Opt) ->
Fun = fun() ->
maybe_register(Reporter, Opt),
{ok, Mod, St} = reporter_init(Reporter, Opt),
- reporter_loop(Mod, St)
+ reporter_loop(Mod, St)
end,
Pid = proc_lib:spawn(Fun),
MRef = erlang:monitor(process, Pid),
@@ -1357,20 +1430,21 @@ terminate_reporter(#reporter{pid = undefined}) ->
subscribe_(Reporter, Metric, DataPoint, Interval, RetryFailedMetrics,
- Extra, Status) ->
+ Extra, Status) ->
Key = #key{reporter = Reporter,
- metric = Metric,
- datapoint = DataPoint,
- extra = Extra,
- retry_failed_metrics = RetryFailedMetrics
- },
+ metric = Metric,
+ datapoint = DataPoint,
+ extra = Extra,
+ retry_failed_metrics = RetryFailedMetrics
+ },
ets:insert(?EXOMETER_SUBS,
- #subscriber{key = Key,
- interval = Interval,
- t_ref = maybe_send_after(Status, Key, Interval)}).
+ #subscriber{key = Key,
+ interval = Interval,
+ t_ref = maybe_send_after(Status, Key, Interval)}).
maybe_send_after(enabled, Key, Interval) when is_integer(Interval) ->
- erlang:send_after(Interval, self(), {report, Key, Interval});
+ erlang:send_after(
+ Interval, self(), subscr_timer_msg(Key, Interval));
maybe_send_after(_, _, _) ->
undefined.
@@ -1378,19 +1452,19 @@ unsubscribe_(Reporter, Metric, DataPoint, Extra) ->
?info("unsubscribe_(~p, ~p, ~p, ~p)~n",
[ Reporter, Metric, DataPoint, Extra]),
case ets:lookup(?EXOMETER_SUBS, #key{reporter = Reporter,
- metric = Metric,
- datapoint = DataPoint,
- extra = Extra}) of
- [#subscriber{} = Sub] ->
- unsubscribe_(Sub);
+ metric = Metric,
+ datapoint = DataPoint,
+ extra = Extra}) of
+ [#subscriber{} = Sub] ->
+ unsubscribe_(Sub);
[] ->
not_found
end.
unsubscribe_(#subscriber{key = #key{reporter = Reporter,
- metric = Metric,
- datapoint = DataPoint,
- extra = Extra} = Key, t_ref = TRef}) ->
+ metric = Metric,
+ datapoint = DataPoint,
+ extra = Extra} = Key, t_ref = TRef}) ->
try_send(
Reporter, {exometer_unsubscribe, Metric, DataPoint, Extra}),
cancel_timer(TRef),
@@ -1400,12 +1474,12 @@ unsubscribe_(#subscriber{key = #key{reporter = Reporter,
report_values(Found, #key{reporter = Reporter, extra = Extra} = Key) ->
try
- [[report_value(Reporter, Name, DP, Extra, Val)
- || {DP, Val} <- Values] || {Name, Values} <- Found]
+ [[report_value(Reporter, Name, DP, Extra, Val)
+ || {DP, Val} <- Values] || {Name, Values} <- Found]
catch
- error:Reason ->
- lager:error("ERROR ~p~nKey = ~p~nTrace: ~p",
- [Reason, Key, erlang:get_stacktrace()])
+ error:Reason ->
+ lager:error("ERROR ~p~nKey = ~p~nTrace: ~p",
+ [Reason, Key, erlang:get_stacktrace()])
end.
report_value(Reporter, Metric, DataPoint, Extra, Val) ->
@@ -1418,9 +1492,9 @@ report_value(Reporter, Metric, DataPoint, Extra, Val) ->
retrieve_metric({Metric, Type, Enabled}, Acc) ->
Cands = ets:select(
- ?EXOMETER_SUBS,
- [{#subscriber{key = #key{metric = Metric, _='_'},
- _ = '_'}, [], ['$_']}]),
+ ?EXOMETER_SUBS,
+ [{#subscriber{key = #key{metric = Metric, _='_'},
+ _ = '_'}, [], ['$_']}]),
[ { Metric, exometer:info(Metric, datapoints),
get_subscribers(Metric, Type, Enabled, Cands), Enabled } | Acc ].
@@ -1437,12 +1511,12 @@ get_subscribers(_Metric, _Type, _Status, []) ->
%% This subscription matches Metric
get_subscribers(Metric, Type, Status,
- [ #subscriber {
- key = #key {
- reporter = SReporter,
- metric = Metric,
- datapoint = SDataPoint
- }} | T ]) ->
+ [ #subscriber {
+ key = #key {
+ reporter = SReporter,
+ metric = Metric,
+ datapoint = SDataPoint
+ }} | T ]) ->
?debug("get_subscribers(~p, ~p, ~p): match~n", [ Metric, SDataPoint, SReporter]),
[ { SReporter, SDataPoint } | get_subscribers(Metric, Type, Status, T) ];
@@ -1463,14 +1537,14 @@ get_subscribers(Metric, Type, Status,
%% This subscription does not match Metric.
get_subscribers(Metric, Type, Status,
- [ #subscriber {
- key = #key {
- reporter = SReporter,
- metric = SMetric,
- datapoint = SDataPoint
- }} | T]) ->
+ [ #subscriber {
+ key = #key {
+ reporter = SReporter,
+ metric = SMetric,
+ datapoint = SDataPoint
+ }} | T]) ->
?debug("get_subscribers(~p, ~p, ~p) nomatch(~p) ~n",
- [ SMetric, SDataPoint, SReporter, Metric]),
+ [ SMetric, SDataPoint, SReporter, Metric]),
get_subscribers(Metric, Type, Status, T).
%% Purge all subscriptions associated with a specific reporter
@@ -1482,12 +1556,12 @@ purge_subscriptions(R) ->
%% Return new #subscriber list with all original subscribers
%% that do not reference reporter R.
Subs = ets:select(?EXOMETER_SUBS,
- [{#subscriber{key = #key{reporter = R, _='_'},
- _ = '_'}, [], ['$_']}]),
+ [{#subscriber{key = #key{reporter = R, _='_'},
+ _ = '_'}, [], ['$_']}]),
lists:foreach(fun(#subscriber {key = Key, t_ref = TRef}) ->
- cancel_timer(TRef),
- ets:delete(?EXOMETER_SUBS, Key)
- end, Subs).
+ cancel_timer(TRef),
+ ets:delete(?EXOMETER_SUBS, Key)
+ end, Subs).
%% Called by the spawn_monitor() call in init
%% Loop and run reporters.
@@ -1496,7 +1570,7 @@ reporter_init(Reporter, Opts) ->
Module = proplists:get_value(module, Opts, Reporter),
case Module:exometer_init(Opts) of
{ok, St} ->
- {ok, Module, St};
+ {ok, Module, St};
{error, Reason} ->
?error("Failed to start reporter ~p: ~p~n", [Module, Reason]),
exit(Reason)
@@ -1573,23 +1647,23 @@ cast(Req) ->
init_subscriber({Reporter, Metric, DataPoint, Interval, RetryFailedMetrics}) ->
Status = get_reporter_status(Reporter),
subscribe_(Reporter, Metric, DataPoint, Interval,
- RetryFailedMetrics, undefined, Status);
+ RetryFailedMetrics, undefined, Status);
init_subscriber({Reporter, Metric, DataPoint, Interval,
- RetryFailedMetrics, Extra}) ->
+ RetryFailedMetrics, Extra}) ->
Status = get_reporter_status(Reporter),
subscribe_(Reporter, Metric, DataPoint, Interval,
- RetryFailedMetrics, Extra, Status);
+ RetryFailedMetrics, Extra, Status);
init_subscriber({Reporter, Metric, DataPoint, Interval}) ->
Status = get_reporter_status(Reporter),
subscribe_(Reporter, Metric, DataPoint, Interval,
- true, undefined, Status);
+ true, undefined, Status);
init_subscriber({apply, {M, F, A}}) ->
lists:foreach(fun(Sub) ->
- init_subscriber(Sub)
- end, apply(M, F, A));
+ init_subscriber(Sub)
+ end, apply(M, F, A));
init_subscriber({select, Expr}) when tuple_size(Expr)==3;
- tuple_size(Expr)==4;
- tuple_size(Expr)==5 ->
+ tuple_size(Expr)==4;
+ tuple_size(Expr)==5 ->
{Pattern, Reporter, DataPoint, Interval, Retry, Extra} =
case Expr of
{P, R, D, I} -> {P, R, D, I, true, undefined};
@@ -1601,23 +1675,23 @@ init_subscriber({select, Expr}) when tuple_size(Expr)==3;
lists:foreach(
fun({Entry, _, _}) ->
subscribe_(Reporter, Entry, DataPoint, Interval,
- Retry, Extra, Status)
+ Retry, Extra, Status)
end, Entries);
init_subscriber(Other) ->
?warning("Incorrect static subscriber spec ~p. "
"Use { Reporter, Metric, DataPoint, Interval [, Extra ]}~n",
- [ Other ]).
+ [ Other ]).
get_reporter_status(R) ->
try ets:lookup_element(?EXOMETER_REPORTERS, R, #reporter.status)
catch
- error:_ -> disabled
+ error:_ -> disabled
end.
add_restart(#restart{spec = Spec,
- history = H,
- save_n = N} = R) ->
+ history = H,
+ save_n = N} = R) ->
T = exometer_util:timestamp(),
H1 = lists:sublist([T|H], 1, N),
case match_frequency(H1, Spec) of
@@ -1671,10 +1745,10 @@ restart_rec(L) ->
Save = lists:foldl(
fun
({R,_}, Acc) when is_integer(R) ->
- erlang:max(R, Acc);
+ erlang:max(R, Acc);
(_, Acc) ->
- Acc
- end, 0, L),
+ Acc
+ end, 0, L),
#restart{spec = L, save_n = Save}.
valid_restart(L) when is_list(L) ->
@@ -1692,37 +1766,37 @@ do_remove_reporter(Reporter) ->
do_remove_reporter(Reporter, Terminate) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [#reporter{} = R] ->
+ [#reporter{} = R] ->
case Terminate of
true ->
terminate_reporter(R);
false ->
ok
end,
- ets:delete(?EXOMETER_REPORTERS, Reporter),
- purge_subscriptions(Reporter),
- ok;
+ ets:delete(?EXOMETER_REPORTERS, Reporter),
+ purge_subscriptions(Reporter),
+ ok;
[] ->
{error, not_found}
end.
change_reporter_status(Reporter, New) ->
case ets:lookup(?EXOMETER_REPORTERS, Reporter) of
- [R] -> do_change_reporter_status(R, New);
- [] -> {error, not_found}
+ [R] -> do_change_reporter_status(R, New);
+ [] -> {error, not_found}
end.
do_change_reporter_status(#reporter{name = Reporter,
- status = Old} = R, New) ->
+ status = Old} = R, New) ->
case {Old, New} of
- {disabled, enabled} ->
- restart_reporter(R);
- {enabled, disabled} ->
- cancel_subscr_timers(Reporter),
- terminate_reporter(R),
- ets:update_element(?EXOMETER_REPORTERS,
- Reporter, [{#reporter.status, disabled}]);
- {Old, Old} ->
- ok
+ {disabled, enabled} ->
+ restart_reporter(R);
+ {enabled, disabled} ->
+ cancel_subscr_timers(Reporter),
+ terminate_reporter(R),
+ ets:update_element(?EXOMETER_REPORTERS,
+ Reporter, [{#reporter.status, disabled}]);
+ {Old, Old} ->
+ ok
end,
ok.
diff --git a/src/exometer_report_logger.erl b/src/exometer_report_logger.erl
new file mode 100644
index 0000000..5271aea
--- /dev/null
+++ b/src/exometer_report_logger.erl
@@ -0,0 +1,427 @@
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+%% @doc Exometer report collector and logger.
+%%
+%% This module implements a behavior for collecting reporting data and
+%% handling it (logging to disk or ets, printing to tty, etc.)
+%%
+%% The logger has built-in support for receiving input via UDP, TCP or
+%% internal Erlang messaging, as well as a plugin API for custom input
+%% handling. Correspondingly, it has support for output to TTY or ets, as
+%% well as a plugin API for custom output.
+%%
+%% An example of how the logger can be used can be found in
+%% `test/exometer_test_udp_reporter.erl', which implements a UDP-based
+%% reporter as well as an input plugin and an output plugin. This reporter
+%% is used by `test/exometer_report_SUITE.erl'.
+%%
+%% Loggers can be combined, e.g. by creating one logger that receives Erlang
+%% messages, and other loggers that receive from different sources, prefix
+%% their reports and pass them on to the first logger.
+%%
+%% Input plugins
+%%
+%% An input plugin is initiated by `Module:logger_init_input(State)', where
+%% `State' is whatever was given as a `state' option (default: `undefined').
+%% The function must create a process and return `{ok, Pid}'. `Pid' is
+%% responsible for setting up whatever input channel is desired, and passes
+%% on incoming data to the logger via Erlang messages `{plugin, Pid, Data}'.
+%%
+%% Output Chaining
+%%
+%% Each incoming data item is passed through the list of output operators.
+%% Each output operator is able to modify the data (the `tty' and `ets'
+%% operators leave the data unchanged). Output plugins receive the data
+%% in `Module:logger_handle_data(Data, State)', which must return
+%% `{NewData, NewState}'. The state is private to the plugin, while `NewData'
+%% will be passed along to the next output operator.
+%%
+%% Flow control
+%%
+%% The logger will handle flow control automatically for `udp' and `tcp'
+%% inputs. If `{active,once}' or `{active, false}', the logger will trigger
+%% `{active, once}' each time it has handled an incoming message.
+%% If `{active, N}', it will "refill" the port each time it receives an
+%% indication that it has become passive.
+%%
+%% Input plugins create a process in `Module:logger_init_input/1'. This process
+%% can mimick the behavior of Erlang ports by sending a `{plugin_passive, Pid}'
+%% message to the logger. The logger will reply with a message,
+%% `{plugin_active, N}', where `N' is the value given by the `active' option.
+%% @end
+-module(exometer_report_logger).
+
+-behaviour(gen_server).
+
+-export([new/1]).
+-export([start_link/1]).
+
+-export([info/0,
+ info/1]).
+
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-record(st, {id,
+ input,
+ output}).
+
+-include_lib("parse_trans/include/exprecs.hrl").
+-export_records([tcp, udp, tty, ets, int]).
+
+%% input records
+-record(tcp, {socket, port, options = [], active = once}).
+-record(udp, {socket, port, options = [], active = true}).
+%% output records
+-record(tty, {prefix = []}).
+-record(ets, {tab}).
+%% both input and output
+-record(int, {process}).
+-record(plugin, {module, mod_state, process, active = once}).
+
+-type proplist() :: [{atom(), any()}].
+
+-type logger_info() :: {id, any()}
+ | {input, proplist()}
+ | {output, proplist()}.
+
+-type plugin_state() :: any().
+
+-callback logger_init_input(any()) ->
+ {ok, pid()}.
+-callback logger_init_output(any()) ->
+ {ok, plugin_state()}.
+-callback logger_handle_data(binary(), plugin_state()) ->
+ {binary(), plugin_state()}.
+
+-spec new([{id, any()} | {input, list()} | {output, list()}]) -> {ok,pid()}.
+%% @doc Create a new logger instance.
+%%
+%% This function creates a logger process with the given input and output
+%% parameters.
+%%
+%% * `{id, ID}' is mainly for documentation and simplifying identification
+%% of instances returned by {@link info/0}.
+%% * `{input, PropList}' specifies what the logger listens to. Only the first
+%% `input' entry is regarded, but the option is mandatory.
+%% * `{output, PropList}' specifies what the logger should to with received
+%% data. Multiple `output' entries are allowed, and they will be processed
+%% in the order given.
+%%
+%% Valid input options:
+%%
+%% * `{mode, udp | tcp | internal | plugin}' defines the protocol
+%% * `{active, false | true | once | N}' provides flow control. Default: `true'.
+%% * (mode-specific options)
+%%
+%% Valid output options:
+%%
+%% * `{mode, tty | ets | plugin | internal}' defines output types
+%% * (output-specific options)
+%%
+%% Mode-specific options, `udp':
+%%
+%% * `{port, integer()}' - UDP port number
+%% * `{options, list()}' - Options to pass to {@link gen_udp:open/2}
+%%
+%% Mode-specific options, `tcp':
+%%
+%% * `{port, integer()}' - TCP port number
+%% * `{options, list()}' - Options to pass to {@link gen_tcp:listen/2}
+%%
+%% Mode-specific options, `tty':
+%%
+%% * `{prefix, iolist()}' - Prefix string inserted before the data, which is
+%% printed as-is (note that any delimiter would need to be part of the prefix)
+%%
+%% Mode-specific options, `ets':
+%% * `{table, ets:table()}' - Ets table identifier. If not specified, an
+%% ordered-set table will be created by the logger process. The incoming
+%% data will be inserted as `{erlang:now(), Data}'.
+%%
+%% Mode-specific options, `internal':
+%% * `{process, PidOrRegname}' specifies another logger instance, which is to
+%% receive data from this logger (if used in output), or which is allowed
+%% to send to this logger (if used in input). If no process is given for
+%% input, any process can send data (on the form
+%% `{exometer_report_logger, Pid, Data}') to this logger.
+%%
+%% Mode-specific options, `plugin':
+%%
+%% * `{module, Module}' - name of callback module
+%% (behaviour: `exometer_report_logger')
+%% * `{state, State}' - Passed as initial argument to
+%% `Module:logger_init_input/1' or `Module:logger_init_output/1', depending
+%% on whether the plugin is specified as input or output.
+%% @end
+new(Options) ->
+ supervisor:start_child(exometer_report_logger_sup, [Options]).
+
+-spec start_link(proplist()) -> {ok, pid()}.
+%% @doc Start function for logger instance.
+%%
+%% This function is the start function eventually called as a result from
+%% {@link new/1}, but whereas `new/1' creates a supervised instance, this
+%% function simply creates the process. It would normally not be used directly.
+%% @end
+start_link(Options) ->
+ ID = exometer_util:get_opt(id, Options, undefined),
+ Input = get_input(Options),
+ Output = get_output(Options),
+ gen_server:start_link(?MODULE, {ID, Input, Output}, []).
+
+-spec info() -> [{pid(), [logger_info()]}].
+%% @doc List active logger instances.
+%%
+%% This function lists the instances started via {@link new/1}, along with their
+%% respective settings as nested property lists.
+%% @end
+info() ->
+ [{P, info(P)} || {_, P, _, _} <- supervisor:which_children(
+ exometer_report_logger_sup)].
+-spec info(pid()) -> [logger_info()].
+%% @doc Lists the settings of a given logger instance.
+info(P) ->
+ gen_server:call(P, info).
+
+%% Client-side check
+get_input(Opts) ->
+ L = exometer_util:get_opt(input, Opts),
+ get_input(exometer_util:get_opt(mode, L), L).
+
+get_input(tcp, L) ->
+ Port = exometer_util:get_opt(port, L),
+ Options = exometer_util:get_opt(options, L, []),
+ Active = exometer_util:get_opt(
+ active, L, get_opt_active(Options)),
+ #tcp{port = Port, options = Options, active = Active};
+get_input(udp, L) ->
+ Port = exometer_util:get_opt(port, L),
+ Options = exometer_util:get_opt(options, L, []),
+ Active = exometer_util:get_opt(
+ active, L, get_opt_active(Options)),
+ #udp{port = Port, options = Options, active = Active};
+get_input(internal, L) ->
+ P = exometer_util:get_opt(process, L, undefined),
+ #int{process = P};
+get_input(plugin, L) ->
+ Mod = exometer_util:get_opt(module, L),
+ St = exometer_util:get_opt(state, L, undefined),
+ Active = exometer_util:get_opt(active, L, true),
+ #plugin{module = Mod, mod_state = St, active = Active}.
+
+
+get_opt_active(Opts) ->
+ case lists:keyfind(active, 1, Opts) of
+ {_, true} -> true;
+ {_, N} when is_integer(N) -> N;
+ _ -> once
+ end.
+
+%% Client-side check
+get_output(Opts) ->
+ [get_output(exometer_util:get_opt(mode, O), O) || {output, O} <- Opts].
+
+get_output(tty, O) ->
+ Prefix = exometer_util:get_opt(prefix, O, []),
+ #tty{prefix = Prefix};
+get_output(ets, O) ->
+ Tab = exometer_util:get_opt(tab, O, undefined),
+ #ets{tab = Tab};
+get_output(internal, O) ->
+ P = exometer_util:get_opt(process, O),
+ #int{process = P};
+get_output(plugin, O) ->
+ Mod = exometer_util:get_opt(module, O),
+ St = exometer_util:get_opt(state, O, undefined),
+ #plugin{module = Mod, mod_state = St}.
+
+
+%% Gen_server callbacks
+
+%% @private
+init({ID, Input, Output}) ->
+ NewL = init_input(Input),
+ NewO = init_output(Output),
+ {ok, #st{id = ID,
+ input = NewL,
+ output = NewO}}.
+
+%% @private
+handle_call(info, _, #st{id = ID, input = I, output = O} = S) ->
+ {reply, info_(ID, I, O), S};
+handle_call(_Req, _From, St) ->
+ {reply, {error, unsupported}, St}.
+
+%% @private
+handle_cast({socket, Socket}, #st{input = L} = S) ->
+ case L of
+ #tcp{active = N} = T ->
+ case inet:getopts(Socket, [active]) of
+ {ok, [{active, false}]} ->
+ inet:setopts(Socket, [{active, N}]);
+ _ ->
+ ok
+ end,
+ {noreply, S#st{input = T#tcp{socket = Socket}}};
+ _ ->
+ {noreply, S}
+ end;
+handle_cast(_Msg, St) ->
+ {noreply, St}.
+
+%% @private
+handle_info({tcp, Socket, Data}, #st{input = #tcp{socket = Socket,
+ active = Active},
+ output = Out} = S) ->
+ handle_data(Data, Out),
+ check_active(Socket, Active),
+ {noreply, S};
+handle_info({udp, Socket, _Host, _Port, Data},
+ #st{input = #udp{socket = Socket}, output = Out} = S) ->
+ Out1 = handle_data(Data, Out),
+ {noreply, S#st{output = Out1}};
+handle_info({plugin, Pid, Data}, #st{input = #plugin{process = Pid},
+ output = Out} = S) ->
+ Out1 = handle_data(Data, Out),
+ {noreply, S#st{output = Out1}};
+handle_info({tcp_passive, Socket}, #st{input = #tcp{socket = Socket,
+ active = Active}} = S) ->
+ inet:setopts(Socket, Active),
+ {noreply, S};
+handle_info({udp_passive, Socket}, #st{input = #udp{socket = Socket,
+ active = Active}} = S) ->
+ inet:setopts(Socket, Active),
+ {noreply, S};
+handle_info({plugin_passive, Pid}, #st{input = #plugin{process = Pid,
+ active = Active}} = S) ->
+ Pid ! {plugin_active, Active},
+ {noreply, S};
+handle_info({?MODULE, P, Data}, #st{input = #int{process = Pl},
+ output = Out} = S)
+ when Pl =:= P; Pl =:= undefined ->
+ Out1 = handle_data(Data, Out),
+ {noreply, S#st{output = Out1}};
+handle_info(_, S) ->
+ {noreply, S}.
+
+%% @private
+terminate(_, _) ->
+ ok.
+
+%% @private
+code_change(_FromVsn, S, _Extra) ->
+ {ok, S}.
+
+%% End gen_server callbacks
+
+info_(ID, I, O) ->
+ [{id, ID},
+ {input, ensure_list(pp(I))},
+ {output, ensure_list(pp(O))}].
+
+ensure_list(I) when is_tuple(I) ->
+ [I];
+ensure_list(I) when is_list(I) ->
+ I.
+
+%% Copied from git:uwiger/jobs/src/jobs_info.erl
+pp(L) when is_list(L) ->
+ [pp(X) || X <- L];
+pp(X) ->
+ case '#is_record-'(X) of
+ true ->
+ RecName = element(1,X),
+ {RecName, lists:zip(
+ '#info-'(RecName,fields),
+ pp(tl(tuple_to_list(X))))};
+ false ->
+ if is_tuple(X) ->
+ list_to_tuple(pp(tuple_to_list(X)));
+ true ->
+ X
+ end
+ end.
+
+init_input(#tcp{port = Port,
+ options = Opts} = T) ->
+ _ = spawn_tcp_acceptor(Port, Opts),
+ T;
+init_input(#udp{port = Port, options = Opts} = U) ->
+ {ok, Socket} = gen_udp:open(Port, Opts),
+ U#udp{socket = Socket};
+init_input(#plugin{module = Mod, mod_state = St} = P) ->
+ case Mod:logger_init_input(St) of
+ {ok, Pid} when is_pid(Pid) ->
+ P#plugin{process = Pid}
+ end;
+init_input(#int{} = I) ->
+ I.
+
+
+spawn_tcp_acceptor(Port, Opts) ->
+ Parent = self(),
+ spawn_link(fun() ->
+ {ok, LSock} = gen_tcp:listen(Port, Opts),
+ {ok, Socket} = gen_tcp:accept(LSock),
+ ok = gen_tcp:controlling_process(Socket, Parent),
+ gen_server:cast(Parent, {socket, Socket})
+ end).
+
+init_output(Out) ->
+ [init_output_(O) || O <- Out].
+
+init_output_(#tty{} = TTY) -> TTY;
+init_output_(#int{} = Int) -> Int;
+init_output_(#ets{tab = T} = E) ->
+ Tab = case T of
+ undefined ->
+ ets:new(?MODULE, [ordered_set]);
+ _ ->
+ T
+ end,
+ E#ets{tab = Tab};
+init_output_(#plugin{module = Mod, mod_state = St} = P) ->
+ {ok, St1} = Mod:logger_init_output(St),
+ P#plugin{mod_state = St1}.
+
+check_active(Socket, once) ->
+ inet:setopts(Socket, [{active, once}]);
+check_active(_Socket, _) ->
+ ok.
+
+handle_data(Data, Out) ->
+ handle_data(Data, Out, []).
+
+handle_data(Data, [H|T], Acc) ->
+ {Data1, H1} = handle_data_(Data, H),
+ handle_data(Data1, T, [H1|Acc]);
+handle_data(_, [], Acc) ->
+ lists:reverse(Acc).
+
+handle_data_(Data, #tty{prefix = Pfx} = Out) ->
+ io:fwrite(iolist_to_binary([Pfx, Data, $\n])),
+ {Data, Out};
+handle_data_(Data, #ets{tab = T} = Out) ->
+ ets:insert(T, {erlang:now(), Data}),
+ {Data, Out};
+handle_data_(Data, #int{process = P} = Out) ->
+ try P ! {?MODULE, self(), Data} catch _:_ -> error end,
+ {Data, Out};
+handle_data_(Data, #plugin{module = Mod, mod_state = ModSt} = Out) ->
+ {Data1, ModSt1} = Mod:logger_handle_data(Data, ModSt),
+ {Data1, Out#plugin{mod_state = ModSt1}}.
+
+
+
diff --git a/src/exometer_report_logger_sup.erl b/src/exometer_report_logger_sup.erl
new file mode 100644
index 0000000..0031ced
--- /dev/null
+++ b/src/exometer_report_logger_sup.erl
@@ -0,0 +1,27 @@
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+%% @private
+-module(exometer_report_logger_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+start_link() ->
+ supervisor:start_link({local,?MODULE}, ?MODULE, []).
+
+
+init(_) ->
+ {ok, {{simple_one_for_one, 3, 10},
+ [?CHILD(exometer_report_logger, worker)]}}.
diff --git a/src/exometer_shallowtree.erl b/src/exometer_shallowtree.erl
index ded57e3..354bbf0 100644
--- a/src/exometer_shallowtree.erl
+++ b/src/exometer_shallowtree.erl
@@ -12,20 +12,20 @@
-module(exometer_shallowtree).
-export([new/1,
- insert/3,
- take_min/1,
- to_list/1,
- filter/2,
- size/1,
- limit/1]).
+ insert/3,
+ take_min/1,
+ to_list/1,
+ filter/2,
+ size/1,
+ limit/1]).
-export([fill/1, fill1/2]).
-export_type([tree/0]).
-record(t, {size = 0,
- limit = 10,
- tree = []}).
+ limit = 10,
+ tree = []}).
-type tree() :: #t{}.
@@ -52,11 +52,11 @@ limit(#t{limit = L}) ->
%% @end
insert(K, V, #t{size = X, limit = X, tree = Tr} = T) when is_number(K) ->
case K =< element(1, Tr) of
- true ->
- T;
- false ->
- {_, _, Tr1} = take_min_(Tr),
- T#t{tree = insert_(K, V, Tr1)}
+ true ->
+ T;
+ false ->
+ {_, _, Tr1} = take_min_(Tr),
+ T#t{tree = insert_(K, V, Tr1)}
end;
insert(K, V, #t{size = Sz, tree = Tr} = T) when is_number(K) ->
T#t{size = Sz+1, tree = insert_(K, V, Tr)}.
@@ -71,9 +71,9 @@ insert_(K, V, T ) -> meld(mknode(K, V), T).
%% @end
take_min(#t{size = Sz, tree = Tr} = T) ->
case take_min_(Tr) of
- error -> error;
- {K, V, Tr1} ->
- {K, V, T#t{size = Sz-1, tree = Tr1}}
+ error -> error;
+ {K, V, Tr1} ->
+ {K, V, T#t{size = Sz-1, tree = Tr1}}
end.
take_min_([]) -> error;
@@ -99,16 +99,16 @@ filter(F, #t{tree = T}) -> filter_(F, [T]).
filter_(_, []) -> [];
filter_(F, [{K,V,_,L,R}|T]) ->
case F(K,V) of false -> filter_(F, [L,R|T]);
- {true, Keep} -> [Keep|filter_(F, [L,R|T])]
+ {true, Keep} -> [Keep|filter_(F, [L,R|T])]
end;
filter_(F, [[]|T]) -> filter_(F, T).
meld({K1,V1, _, L1, R1} = T1, {K2,V2, _, L2, R2} = T2) ->
case K1 < K2 of
- true ->
- mknode(K1,V1, L1, meld(R1, T2));
- false ->
- mknode(K2,V2, L2, meld(R2, T1))
+ true ->
+ mknode(K1,V1, L1, meld(R1, T2));
+ false ->
+ mknode(K2,V2, L2, meld(R2, T1))
end;
meld([], T2) -> T2;
meld(T1, []) -> T1;
diff --git a/src/exometer_slide.erl b/src/exometer_slide.erl
index 4f9b4de..a712431 100644
--- a/src/exometer_slide.erl
+++ b/src/exometer_slide.erl
@@ -178,7 +178,7 @@ to_list(#slide{size = Sz, n = N, max_n = MaxN, buf1 = Buf1, buf2 = Buf2}) ->
foldl(_Timestamp, _Fun, _Acc, #slide{size = Sz}) when Sz == 0 ->
[];
foldl(Timestamp, Fun, Acc, #slide{size = Sz, n = N, max_n = MaxN,
- buf1 = Buf1, buf2 = Buf2}) ->
+ buf1 = Buf1, buf2 = Buf2}) ->
Start = Timestamp - Sz,
lists:foldr(
Fun, lists:foldl(Fun, Acc, take_since(
diff --git a/src/exometer_slot_slide.erl b/src/exometer_slot_slide.erl
index eb3f381..e3ac953 100644
--- a/src/exometer_slot_slide.erl
+++ b/src/exometer_slot_slide.erl
@@ -177,7 +177,7 @@
-export([new/2, new/4, new/5,
add_element/2,
add_element/3,
- add_element/4,
+ add_element/4,
reset/1,
to_list/1,
foldl/3,
@@ -265,7 +265,7 @@ add_element(TS, Val, #slide{cur_slot = CurrentSlot,
%% Invoke the sample MFA to get a new state to work with
%%
ret(Wrap, Flag,
- Slide1#slide {cur_state = SampleF(TS, Val, Slide1#slide.cur_state)}).
+ Slide1#slide {cur_state = SampleF(TS, Val, Slide1#slide.cur_state)}).
ret(false, _, Slide) ->
Slide;
@@ -313,8 +313,8 @@ foldr(Fun, Acc, Slide) ->
%% within the timespan ranging from Oldest up to the current timme.
%%
take_since(Oldest, #slide{%% cur_slot = CurrentSlot,
- cur_state = CurrentState,
- slot_period = SlotPeriod } =Slide) ->
+ cur_state = CurrentState,
+ slot_period = SlotPeriod } =Slide) ->
%% Check if we need to add a slot for the current time period
%% before we start to grab data.
@@ -325,9 +325,9 @@ take_since(Oldest, #slide{%% cur_slot = CurrentSlot,
#slide { list1 = List1,
list2 = List2} =
%% if TSSlot =/= CurrentSlot, CurrentState =/= undefined ->
- if CurrentState =/= undefined ->
+ if CurrentState =/= undefined ->
{_, Sl} = add_slot(TS, Slide),
- Sl;
+ Sl;
true ->
Slide
end,
@@ -368,7 +368,7 @@ add_slot(TS, #slide{timespan = TimeSpan,
undefined -> %% Transformation function could not produce an element
%% Reset the time slot to the current slot. Reset state./
{false, Slide#slide{ cur_slot = TSSlot,
- cur_state = undefined}};
+ cur_state = undefined}};
%% The transform function produced an element to store
Element ->
@@ -380,16 +380,16 @@ add_slot(TS, #slide{timespan = TimeSpan,
%% Shift list1 into list2.
%% Add the new slot as the initial element of list1
{true, Slide#slide{ list1 = [{ CurrentSlot, Element }],
- list2 = List1,
- list1_start_slot = CurrentSlot,
- cur_slot = TSSlot,
- cur_state = undefined}};
+ list2 = List1,
+ list1_start_slot = CurrentSlot,
+ cur_slot = TSSlot,
+ cur_state = undefined}};
true ->
%% No shift necessary. Tack on the new slot to list1.
{false,
- Slide#slide{list1 = [{CurrentSlot, Element} | List1],
- cur_slot = TSSlot,
- cur_state = undefined}}
+ Slide#slide{list1 = [{CurrentSlot, Element} | List1],
+ cur_slot = TSSlot,
+ cur_state = undefined}}
end
end.
diff --git a/src/exometer_spiral.erl b/src/exometer_spiral.erl
index a4bbb9d..17aadce 100644
--- a/src/exometer_spiral.erl
+++ b/src/exometer_spiral.erl
@@ -13,16 +13,16 @@
%% exometer_entry callbacks
-export([behaviour/0,
- probe_init/3,
- probe_terminate/1,
- probe_setopts/3,
- probe_update/2,
- probe_get_value/2,
- probe_get_datapoints/1,
- probe_reset/1,
- probe_code_change/3,
- probe_sample/1,
- probe_handle_msg/2]).
+ probe_init/3,
+ probe_terminate/1,
+ probe_setopts/3,
+ probe_update/2,
+ probe_get_value/2,
+ probe_get_datapoints/1,
+ probe_reset/1,
+ probe_code_change/3,
+ probe_sample/1,
+ probe_handle_msg/2]).
%% exometer_proc callback
-export([count_sample/3,
@@ -64,7 +64,7 @@ probe_init(Name, _Type, Options) ->
{ok, St#st{slide = Slide}}.
probe_terminate(_St) ->
- ok.
+ ok.
probe_get_value(DataPoints, St) ->
{ok, [get_single_value(St, DataPoint) || DataPoint <- DataPoints]}.
@@ -77,8 +77,8 @@ probe_setopts(_Entry, _Options, _St) ->
probe_update(Increment, #st{slide = Slide, total = Total} = St) ->
{ok, St#st{
- slide = exometer_slot_slide:add_element(Increment, Slide),
- total = Total + Increment}}.
+ slide = exometer_slot_slide:add_element(Increment, Slide),
+ total = Total + Increment}}.
probe_reset(#st{slide = Slide} = St) ->
{ok, St#st{total = 0, slide = exometer_slot_slide:reset(Slide)}}.
@@ -104,14 +104,14 @@ process_opts(St, Options) ->
%% Unknown option, pass on to State options list, replacing
%% any earlier versions of the same option.
({Opt, Val}, St1) ->
- St1#st{opts = [{Opt, Val}
- | lists:keydelete(Opt, 1, St1#st.opts)]}
- end, St, Options).
+ St1#st{opts = [{Opt, Val}
+ | lists:keydelete(Opt, 1, St1#st.opts)]}
+ end, St, Options).
%% Simple sample processor that maintains a counter.
%% of all
count_sample(_TS, Increment, undefined) ->
- Increment;
+ Increment;
count_sample(_TS, Increment, Total) ->
Total + Increment.
@@ -130,6 +130,6 @@ get_single_value(St, count) ->
get_single_value(St, one) ->
{one, exometer_slot_slide:foldl(fun({_TS, Val}, Acc) -> Acc + Val end,
- 0, St#st.slide) };
+ 0, St#st.slide) };
get_single_value(_St, Unsupported) ->
{Unsupported, {error, unsupported}}.
diff --git a/src/exometer_uniform.erl b/src/exometer_uniform.erl
index 79bae21..d716461 100644
--- a/src/exometer_uniform.erl
+++ b/src/exometer_uniform.erl
@@ -14,7 +14,7 @@
%% exometer_probe callbacks
-export([behaviour/0,
- probe_init/3,
+ probe_init/3,
probe_terminate/1,
probe_get_value/2,
probe_get_datapoints/1,
@@ -67,9 +67,9 @@ probe_terminate(ModSt) ->
probe_get_value(DataPoints, St) ->
{Length, Total, Lst} = ets:foldl(
- fun(#elem { val = Val }, {Length, Total, List}) ->
- { Length + 1, Total + Val, [ Val | List ]} end,
- {0, 0.0, []}, St#st.ets_ref),
+ fun(#elem { val = Val }, {Length, Total, List}) ->
+ { Length + 1, Total + Val, [ Val | List ]} end,
+ {0, 0.0, []}, St#st.ets_ref),
Sorted = lists:sort(Lst),
Mean = case Length of
@@ -124,9 +124,9 @@ process_opts(St, Options) ->
%% Unknown option, pass on to State options list, replacing
%% any earlier versions of the same option.
({Opt, Val}, St1) ->
- St1#st{ opts = [ {Opt, Val}
- | lists:keydelete(Opt, 1, St1#st.opts) ] }
- end, St, Options).
+ St1#st{ opts = [ {Opt, Val}
+ | lists:keydelete(Opt, 1, St1#st.opts) ] }
+ end, St, Options).
probe_handle_msg(_, S) ->
diff --git a/src/exometer_util.erl b/src/exometer_util.erl
index dda73fd..04965ef 100644
--- a/src/exometer_util.erl
+++ b/src/exometer_util.erl
@@ -15,6 +15,7 @@
[
timestamp/0,
timestamp_to_datetime/1,
+ get_opt/2,
get_opt/3,
get_env/2,
tables/0,
@@ -22,6 +23,7 @@
get_statistics/3,
get_statistics2/4,
pick_items/2,
+ perc/2,
histogram/1,
histogram/2,
drop_duplicates/1,
@@ -32,7 +34,8 @@
set_status/2,
set_event_flag/2,
clear_event_flag/2,
- test_event_flag/2
+ test_event_flag/2,
+ ensure_all_started/1
]).
-export_type([timestamp/0]).
@@ -58,7 +61,7 @@ timestamp() ->
(MS-1258)*1000000000 + S*1000 + US div 1000.
-spec timestamp_to_datetime(timestamp()) ->
- {calendar:datetime(), non_neg_integer()}.
+ {calendar:datetime(), non_neg_integer()}.
%% @doc Convert timestamp to a regular datetime.
%%
%% The timestamp is expected
@@ -76,18 +79,25 @@ get_env(Key, Default) ->
{ok, Value} ->
Value;
_ ->
- case get_env1(exometer_core, Key) of
- {ok, CoreValue} ->
- CoreValue;
- _ ->
- Default
- end
+ case get_env1(exometer_core, Key) of
+ {ok, CoreValue} ->
+ CoreValue;
+ _ ->
+ Default
+ end
end.
get_env1(App, Key) ->
case application:get_env(App, Key) of
- {ok, undefined} -> undefined;
- Other -> Other
+ {ok, undefined} -> undefined;
+ Other -> Other
+ end.
+
+get_opt(K, Opts) ->
+ case lists:keyfind(K, 1, Opts) of
+ {_, V} -> V;
+ false ->
+ error({required, K})
end.
get_opt(K, Opts, Default) ->
@@ -182,15 +192,15 @@ drop_duplicates(List0) when is_list(List0) ->
List1 = lists:foldl(
fun
(Elem, Acc) when is_tuple(Elem) ->
- case lists:keymember(element(1, Elem), 1, Acc) of
- true ->
- Acc;
- false ->
- [Elem | Acc]
- end;
+ case lists:keymember(element(1, Elem), 1, Acc) of
+ true ->
+ Acc;
+ false ->
+ [Elem | Acc]
+ end;
(_, Acc) ->
- Acc
- end, [], List0),
+ Acc
+ end, [], List0),
lists:reverse(List1);
drop_duplicates(Any) ->
Any.
@@ -206,7 +216,7 @@ histogram(Values, default) ->
histogram(Values, DataPoints) ->
H = histogram(Values),
[DP || {K,_} = DP <- H,
- lists:member(K, DataPoints)].
+ lists:member(K, DataPoints)].
-spec get_statistics(Length::non_neg_integer(),
Total::non_neg_integer(),
@@ -247,10 +257,10 @@ get_statistics2(L, Sorted, Total, Mean) ->
Items = [{min,1}, {50, P50}, {median, P50}, {75, perc(0.75,L)},
{90, perc(0.9,L)}, {95, perc(0.95,L)}, {99, perc(0.99,L)},
{999, perc(0.999,L)}, {max,L}],
- [{n,L}, {mean, Mean}, {total, Total} | pick_items(Sorted, 1, Items)].
+ [{n,L}, {mean, Mean}, {total, Total} | pick_items(Sorted, Items)].
-spec pick_items([number()], [{atom() | integer(), integer()}]) ->
- [{atom(), number()}].
+ [{atom(), number()}].
%% @doc Pick values from specified positions in a sorted list of numbers.
%%
%% This function is used to extract datapoints (usually percentiles) from
@@ -304,16 +314,16 @@ key_match(_, _) -> false.
get_datapoints(#exometer_entry{module = exometer,
- type = T}) when T==counter;
+ type = T}) when T==counter;
T==fast_counter;
T==gauge ->
[value, ms_since_reset];
get_datapoints(#exometer_entry{behaviour = entry,
- name = Name, module = M,
- type = Type, ref = Ref}) ->
+ name = Name, module = M,
+ type = Type, ref = Ref}) ->
M:get_datapoints(Name, Type, Ref);
get_datapoints(#exometer_entry{behaviour = probe,
- name = Name, type = Type, ref = Ref}) ->
+ name = Name, type = Type, ref = Ref}) ->
exometer_probe:get_datapoints(Name, Type, Ref).
set_call_count({M, F}, Bool) ->
@@ -326,9 +336,9 @@ get_status(enabled) -> enabled;
get_status(disabled) -> disabled;
get_status(St) when is_integer(St) ->
if St band 2#1 == 1 ->
- enabled;
+ enabled;
true ->
- disabled
+ disabled
end.
set_status(enabled , enabled ) -> 1;
@@ -352,6 +362,35 @@ clear_event_flag(update, disabled) -> 0.
test_event_flag(update, St) when St band 2#10 =:= 2#10 -> true;
test_event_flag(update, _) -> false.
+%% This implementation is originally from Basho's Webmachine. On
+%% older versions of Erlang, we don't have
+%% application:ensure_all_started, so we use this wrapper function to
+%% either use the native implementation or our own version, depending
+%% on what's available.
+-spec ensure_all_started(atom()) -> {ok, [atom()]} | {error, term()}.
+ensure_all_started(App) ->
+ %% Referencing application:ensure_all_started/1 will anger Xref
+ %% in earlier R16B versions of OTP
+ ensure_all_started(App, []).
+
+%% This implementation is originally from Basho's
+%% Webmachine. Reimplementation of ensure_all_started. NOTE this does
+%% not behave the same as the native version in all cases, but as a
+%% quick hack it works well enough for our purposes. Eventually I
+%% assume we'll drop support for older versions of Erlang and this can
+%% be eliminated.
+ensure_all_started(App, Apps0) ->
+ case application:start(App) of
+ ok ->
+ {ok, lists:reverse([App | Apps0])};
+ {error,{already_started,App}} ->
+ {ok, lists:reverse(Apps0)};
+ {error,{not_started,BaseApp}} ->
+ {ok, Apps} = ensure_all_started(BaseApp, Apps0),
+ ensure_all_started(App, [BaseApp|Apps])
+ end.
+
+
%% EUnit tests
-ifdef(TEST).
@@ -363,7 +402,7 @@ key_match_test() ->
{ok,yes} = report_type([a,b,c], [], [{[a,b,'_'], yes}]),
{ok,yes} = report_type([a,b,c], [], [{[a,'_',c], yes}]),
{ok,yes} = report_type([a,b,c], [], [{[a,b|'_'], yes}]),
- {ok,yes} = report_type([a,b,c], [{type,yes}], [{[a,b,c], no}]),
+ {ok,yes} = report_type([a,b,c], [{report_type,yes}], [{[a,b,c], no}]),
ok.
-endif.
diff --git a/test/ct.config b/test/ct.config
new file mode 100644
index 0000000..62f5cfa
--- /dev/null
+++ b/test/ct.config
@@ -0,0 +1,8 @@
+%% -*- erlang -*-
+{lager, [
+ {handlers, [
+ {lager_console_backend, debug},
+ {lager_file_backend, [{file, "error.log"}, {level, error}]},
+ {lager_file_backend, [{file, "console.log"}, {level, debug}]}
+ ]}
+]}.
diff --git a/test/exometer_SUITE.erl b/test/exometer_SUITE.erl
index 8223754..2ecb276 100644
--- a/test/exometer_SUITE.erl
+++ b/test/exometer_SUITE.erl
@@ -23,10 +23,12 @@
test_std_counter/1,
test_gauge/1,
test_fast_counter/1,
+ test_wrapping_counter/1,
test_update_or_create/1,
test_update_or_create2/1,
test_default_override/1,
test_std_histogram/1,
+ test_slot_histogram/1,
test_std_duration/1,
test_folsom_histogram/1,
test_aggregate/1,
@@ -47,6 +49,8 @@
vals/0
]).
+-import(exometer_test_util, [majority/2]).
+
-include_lib("common_test/include/ct.hrl").
%%%===================================================================
@@ -67,7 +71,8 @@ groups() ->
[
test_std_counter,
test_gauge,
- test_fast_counter
+ test_fast_counter,
+ test_wrapping_counter
]},
{test_defaults, [shuffle],
[
@@ -78,6 +83,7 @@ groups() ->
{test_histogram, [shuffle],
[
test_std_histogram,
+ test_slot_histogram,
test_std_duration,
test_folsom_histogram,
test_aggregate,
@@ -109,52 +115,65 @@ init_per_testcase(Case, Config) when
Case == test_folsom_histogram;
Case == test_history1_folsom;
Case == test_history4_folsom ->
+ {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core),
application:start(bear),
application:start(folsom),
- exometer:start(),
- Config;
+ [{started_apps, StartedApps} | Config];
init_per_testcase(Case, Config) when
Case == test_ext_predef;
Case == test_function_match ->
ok = application:set_env(stdlib, exometer_predefined, {script, "../../test/data/test_defaults.script"}),
- ok = application:start(setup),
- exometer:start(),
- Config;
+ {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core),
+ ct:log("StartedApps = ~p~n", [StartedApps]),
+ [{started_apps, StartedApps} | Config];
init_per_testcase(test_app_predef, Config) ->
compile_app1(Config),
- exometer:start(),
+ {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core),
+ ct:log("StartedApps = ~p~n", [StartedApps]),
Scr = filename:join(filename:dirname(
- filename:absname(?config(data_dir, Config))),
- "data/app1.script"),
+ filename:absname(?config(data_dir, Config))),
+ "data/app1.script"),
ok = application:set_env(app1, exometer_predefined, {script, Scr}),
- Config;
+ [{started_apps, StartedApps} | Config];
init_per_testcase(_Case, Config) ->
- exometer:start(),
- Config.
+ {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core),
+ ct:log("StartedApps = ~p~n", [StartedApps]),
+ [{started_apps, StartedApps} | Config].
-end_per_testcase(Case, _Config) when
+end_per_testcase(Case, Config) when
Case == test_folsom_histogram;
Case == test_history1_folsom;
Case == test_history4_folsom ->
- exometer:stop(),
+ _ = stop_started_apps(Config),
folsom:stop(),
application:stop(bear),
ok;
-end_per_testcase(Case, _Config) when
+end_per_testcase(Case, Config) when
Case == test_ext_predef;
Case == test_function_match ->
- ok = application:unset_env(common_test, exometer_predefined),
- exometer:stop(),
- ok = application:stop(setup),
+ ok = application:unset_env(stdlib, exometer_predefined),
+ _ = stop_started_apps(Config),
ok;
-end_per_testcase(test_app_predef, _Config) ->
+end_per_testcase(test_app_predef, Config) ->
+ ok = application:unset_env(app1, exometer_predefined),
ok = application:stop(app1),
- exometer:stop(),
+ _ = stop_started_apps(Config),
ok;
-end_per_testcase(_Case, _Config) ->
- exometer:stop(),
+end_per_testcase(_Case, Config) ->
+ _ = stop_started_apps(Config),
ok.
+stop_started_apps(Config) ->
+ [stop_app(App) ||
+ App <- lists:reverse(?config(started_apps, Config))].
+
+stop_app(App) ->
+ case application:stop(App) of
+ ok -> ok;
+ {error, {not_started, _}} ->
+ ok
+ end.
+
%%%===================================================================
%%% Test Cases
%%%===================================================================
@@ -190,6 +209,25 @@ test_fast_counter(_Config) ->
{ok, [{value, 2}, {ms_since_reset, _}]} = exometer:get_value(C),
ok.
+test_wrapping_counter(_Config) ->
+ C = [?MODULE, ctr, ?LINE],
+ ok = exometer:new(C, counter, []),
+ Max16 = 65534,
+ Max32 = 4294967294,
+ Max64 = 18446744073709551614,
+ Max64p1 = 18446744073709551615,
+ Max64p21 = 18446744073709551635,
+ ok = exometer:update(C, Max64),
+ {ok, [{value, Max64}, {value16, Max16}, {value32, Max32}, {value64, Max64}]} =
+ exometer:get_value(C, [value, value16, value32, value64]),
+ ok = exometer:update(C, 1),
+ {ok, [{value, Max64p1}, {value16, 0}, {value32, 0}, {value64, 0}]} =
+ exometer:get_value(C, [value, value16, value32, value64]),
+ [ok = exometer:update(C, 1) || _ <- lists:seq(1, 20)],
+ {ok, [{value, Max64p21}, {value16, 20}, {value32, 20}, {value64, 20}]} =
+ exometer:get_value(C, [value, value16, value32, value64]),
+ ok.
+
test_update_or_create(_Config) ->
{error, not_found} = exometer:update([a,b,c], 2),
{error, no_template} = exometer:update_or_create([a,b,c], 10),
@@ -240,6 +278,24 @@ test_std_histogram(_Config) ->
{50,2},{75,3},{90,4},{95,5},{99,8},{999,9}] = scale_mean(DPs),
ok.
+test_slot_histogram(Config) ->
+ C = [?MODULE, hist, ?LINE],
+ majority(fun test_slot_histogram_/1, [{metric_name, C}|Config]).
+
+test_slot_histogram_({cleanup, Config}) ->
+ C = ?config(metric_name, Config),
+ exometer:delete(C);
+test_slot_histogram_(Config) ->
+ C = ?config(metric_name, Config),
+ ok = exometer:new(C, histogram, [{histogram_module, exometer_slot_slide},
+ {keep_high, 100},
+ {truncate, false}]),
+ [ok = update_(C,V) || V <- vals()],
+ {_, {ok,DPs}} = timer:tc(exometer, get_value, [C]),
+ [{n,_},{mean,2126866},{min,1},{max,9},{median,2},
+ {50,2},{75,3},{90,4},{95,5},{99,8},{999,9}] = scale_mean(DPs),
+ ok.
+
test_std_duration(_Config) ->
C = [?MODULE, dur, ?LINE],
ok = exometer:new(C, duration, []),
diff --git a/test/exometer_alias_SUITE.erl b/test/exometer_alias_SUITE.erl
new file mode 100644
index 0000000..66c7c51
--- /dev/null
+++ b/test/exometer_alias_SUITE.erl
@@ -0,0 +1,168 @@
+-module(exometer_alias_SUITE).
+
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+%% common_test exports
+-export(
+ [
+ all/0, groups/0, suite/0,
+ init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2
+ ]).
+
+%% test case exports
+-export(
+ [
+ test_register_alias/1,
+ test_load_map/1,
+ test_unload_map/1,
+ test_map_duplicates/1,
+ test_map_badargs/1,
+ test_map_existing/1,
+ test_map_multiple_errors/1
+ ]).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%%===================================================================
+%%% common_test API
+%%%===================================================================
+
+all() ->
+ [
+ {group, test_alias},
+ {group, test_map}
+ ].
+
+groups() ->
+ [
+ {test_alias, [shuffle],
+ [
+ test_register_alias
+ ]},
+ {test_map, [shuffle],
+ [
+ test_load_map,
+ test_unload_map,
+ test_map_duplicates,
+ test_map_badargs,
+ test_map_existing,
+ test_map_multiple_errors
+ ]}
+ ].
+
+suite() ->
+ [].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(Case, Config) ->
+ {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core),
+ [{started_apps, StartedApps} | Config].
+
+end_per_testcase(_Case, Config) ->
+ [application:stop(App) || App <- ?config(started_apps, Config)],
+ ok.
+
+%%%===================================================================
+%%% Test Cases
+%%%===================================================================
+test_register_alias(_Config) ->
+ M = [?MODULE, ?LINE],
+ ok = exometer_alias:new(t1, M, value),
+ {M, value} = exometer_alias:resolve(t1),
+ ok = exometer_alias:delete(t1),
+ error = exometer_alias:resolve(t1),
+ ok.
+
+test_load_map(_Config) ->
+ ok = exometer_alias:load(fun map1/0),
+ {[map1,1], a} = exometer_alias:resolve(m11a),
+ {[map1,1], b} = exometer_alias:resolve(m11b),
+ {[map1,2], a} = exometer_alias:resolve(m12a),
+ {[map1,2], b} = exometer_alias:resolve(m12b),
+ ok.
+
+test_unload_map(Config) ->
+ ok = test_load_map(Config),
+ ok = exometer_alias:unload(fun map1/0),
+ error = exometer_alias:resolve(m11a),
+ error = exometer_alias:resolve(m11b),
+ error = exometer_alias:resolve(m12a),
+ error = exometer_alias:resolve(m12b),
+ ok.
+
+test_map_duplicates(_Config) ->
+ {error, {map_error, [{duplicate_aliases,
+ [{m21a, [{[map2,1], a},
+ {[map2,1], b}]}]
+ }]}} =
+ exometer_alias:load(fun map2/0),
+ ok.
+
+test_map_badargs(_Config) ->
+ {error, {map_error, [{invalid,
+ [{m31a,[{{map3,1},a}]}]
+ }]}} =
+ exometer_alias:load(fun map3/0),
+ ok.
+
+test_map_existing(_Config) ->
+ exometer_alias:new(m11a, [some,metric], a),
+ {error, {map_error, [{existing_aliases,
+ [{m11a, [{[some,metric], a}]}]}
+ ]}} =
+ exometer_alias:load(fun map1/0),
+ ok.
+
+test_map_multiple_errors(_Config) ->
+ exometer_alias:new(m11a, [some,other,metric], a),
+ Map = fun() ->
+ map1() ++ map2() ++ map3()
+ end,
+ {error, {map_error, Errors}} = exometer_alias:load(Map),
+ [
+ {duplicate_aliases, [
+ {m21a,[{[map2,1],a},{[map2,1], b}]}
+ ]},
+ {existing_aliases, [
+ {m11a, [{[some,other,metric], a}]}
+ ]},
+ {invalid, [
+ {m31a, [{{map3,1},a}]}
+ ]}
+ ] = lists:sort(Errors),
+ ok.
+
+map1() ->
+ [
+ {[map1, 1], [{a, m11a},
+ {b, m11b}]},
+ {[map1, 2], [{a, m12a},
+ {b, m12b}]}
+ ].
+
+%% contains error: duplicate aliases
+map2() ->
+ [
+ {[map2, 1], [{a, m21a},
+ {b, m21a}]},
+ {[map2, 2], [{a, m22a},
+ {b, m22b}]}
+ ].
+
+map3() ->
+ [
+ {{map3, 1}, [{a, m31a}]}
+ ].
diff --git a/test/exometer_error_SUITE.erl b/test/exometer_error_SUITE.erl
new file mode 100644
index 0000000..d3a50f8
--- /dev/null
+++ b/test/exometer_error_SUITE.erl
@@ -0,0 +1,186 @@
+-module(exometer_error_SUITE).
+
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+%% common_test exports
+-export(
+ [
+ all/0, groups/0, suite/0,
+ init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2
+ ]).
+
+%% test case exports
+-export(
+ [
+ test_failing_probe/1,
+ test_escalation_1/1,
+ test_escalation_2/1
+ ]).
+
+-import(exometer_test_util, [majority/2]).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%%===================================================================
+%%% common_test API
+%%%===================================================================
+
+all() ->
+ [
+ {group, test_probes}
+ ].
+
+groups() ->
+ [
+ {test_probes, [shuffle],
+ [
+ test_failing_probe,
+ test_escalation_1,
+ test_escalation_2
+ ]}
+ ].
+
+suite() ->
+ [].
+
+init_per_suite(Config) ->
+ _ = application:stop(exometer_core),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(_Case, Config) ->
+ {ok, Started} = exometer_test_util:ensure_all_started(exometer_core),
+ ct:log("Started: ~p~n", [[{T, catch ets:tab2list(T)}
+ || T <- exometer_util:tables()]]),
+ [{started_apps, Started}|Config].
+
+end_per_testcase(_Case, Config) ->
+ ct:log("end_per_testcase(Config = ~p)~n", [Config]),
+ stop_started_apps(Config),
+ ok.
+
+stop_started_apps(Config) ->
+ [stop_app(A) || A <- lists:reverse(?config(started_apps, Config))],
+ ok.
+
+stop_app(App) ->
+ case application:stop(App) of
+ ok -> ok;
+ {error, {not_started,_}} -> ok
+ end.
+
+%%%===================================================================
+%%% Test Cases
+%%%===================================================================
+test_failing_probe(Config) ->
+ M = [?MODULE, ?LINE],
+ majority(fun test_failing_probe_/1, [{metric_name, M}|Config]).
+
+test_failing_probe_({cleanup, Config}) ->
+ M = ?config(metric_name, Config),
+ exometer:delete(M);
+test_failing_probe_(Config) ->
+ M = ?config(metric_name, Config),
+ ok = exometer:new(M, histogram, []),
+ true = killed_probe_restarts(M),
+ ok.
+
+test_escalation_1(Config) ->
+ M = [?MODULE, ?LINE],
+ majority(fun test_escalation_1_/1, [{metric_name, M}|Config]).
+
+test_escalation_1_({cleanup, Config}) ->
+ exometer:delete(?config(metric_name, Config));
+test_escalation_1_(Config) ->
+ M = ?config(metric_name, Config),
+ Levels = [{{3,5000}, restart},
+ {'_', disable}],
+ ok = exometer:new(M, histogram, [{restart, Levels}]),
+ true = killed_probe_restarts(M),
+ true = killed_probe_restarts(M),
+ true = killed_probe_restarts(M),
+ true = killed_probe_disabled(M),
+ ok.
+
+test_escalation_2(Config) ->
+ M = [?MODULE, ?LINE],
+ majority(fun test_escalation_2_/1, [{metric_name, M}|Config]).
+
+test_escalation_2_({cleanup, Config}) ->
+ exometer:delete(?config(metric_name, Config));
+test_escalation_2_(Config) ->
+ M = ?config(metric_name, Config),
+ Levels = [{{3,5000}, restart},
+ {'_', delete}],
+ ok = exometer:new(M, histogram, [{restart, Levels}]),
+ true = killed_probe_restarts(M),
+ true = killed_probe_restarts(M),
+ true = killed_probe_restarts(M),
+ true = killed_probe_deleted(M),
+ ok.
+
+killed_probe_restarts(M) ->
+ Pid = exometer:info(M, ref),
+ ct:log("Pid = ~p~n", [Pid]),
+ exit(Pid, kill),
+ await_death(Pid),
+ NewPid = exometer:info(M, ref),
+ ct:log("NewPid = ~p~n", [NewPid]),
+ enabled = exometer:info(M, status),
+ true = Pid =/= NewPid.
+
+killed_probe_disabled(M) ->
+ Pid = exometer:info(M, ref),
+ ct:log("Pid = ~p~n", [Pid]),
+ exit(Pid, kill),
+ await_death(Pid),
+ undefined = exometer:info(M, ref),
+ ct:log("Ref = undefined~n", []),
+ disabled = exometer:info(M, status),
+ true.
+
+killed_probe_deleted(M) ->
+ Pid = exometer:info(M, ref),
+ ct:log("Pid = ~p~n", [Pid]),
+ exit(Pid, kill),
+ await_death(Pid),
+ ct:log("Ets = ~p~n", [[{T,ets:tab2list(T)} ||
+ T <- exometer_util:tables()]]),
+ {error, not_found} = exometer:get_value(M),
+ ct:log("~p deleted~n", [M]),
+ true.
+
+await_death(Pid) ->
+ Ref = erlang:send_after(1000, self(), zombie),
+ await_death(Pid, Ref),
+ %% Now ping exometer_admin twice to give it time to handle the DOWN msg
+ sys:get_status(exometer_admin),
+ sys:get_status(exometer_admin),
+ ok.
+
+await_death(Pid, Ref) ->
+ case erlang:read_timer(Ref) of
+ false ->
+ error({process_not_dead, Pid});
+ _ ->
+ case erlang:is_process_alive(Pid) of
+ true ->
+ erlang:bump_reductions(500),
+ await_death(Pid, Ref);
+ false ->
+ erlang:cancel_timer(Ref),
+ sys:get_status(exometer_admin),
+ sys:get_status(exometer_admin),
+ ok
+ end
+ end.
diff --git a/test/exometer_report_SUITE.erl b/test/exometer_report_SUITE.erl
new file mode 100644
index 0000000..d9b577c
--- /dev/null
+++ b/test/exometer_report_SUITE.erl
@@ -0,0 +1,244 @@
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+-module(exometer_report_SUITE).
+
+%% common_test exports
+-export(
+ [
+ all/0, groups/0, suite/0,
+ init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2
+ ]).
+
+%% test case exports
+-export(
+ [
+ test_newentry/1,
+ test_subscribe/1,
+ test_subscribe_find/1,
+ test_subscribe_select/1,
+ test_logger_flow_control/1,
+ test_logger_flow_control_2/1
+ ]).
+
+-behaviour(exometer_report_logger).
+-export([logger_init_output/1,
+ logger_handle_data/2]).
+
+-import(exometer_test_util, [majority/2]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("exometer_core/include/exometer.hrl").
+
+-define(DEFAULT_PORT, 8888).
+
+all() ->
+ [
+ {group, test_reporter},
+ {group, test_logger}
+ ].
+
+groups() ->
+ [
+ {test_reporter, [shuffle],
+ [
+ test_newentry,
+ test_subscribe,
+ test_subscribe_find,
+ test_subscribe_select
+ ]},
+ {test_logger, [],
+ [
+ test_logger_flow_control,
+ test_logger_flow_control_2
+ ]}
+ ].
+
+suite() ->
+ [].
+
+init_per_suite(Config) ->
+ Config.
+
+init_per_testcase(_Case, Config) ->
+ {ok, StartedApps} = exometer_test_util:ensure_all_started(exometer_core),
+ [{started_apps, StartedApps} | Config].
+
+end_per_testcase(_Case, Config) ->
+ stop_started_apps(Config).
+
+end_per_suite(_Config) ->
+ ok.
+
+stop_started_apps(Config) ->
+ [application:stop(App) ||
+ App <- lists:reverse(?config(started_apps, Config))].
+
+test_newentry(Config) ->
+ majority(fun test_newentry_/1, Config).
+
+test_newentry_({cleanup, Config}) ->
+ restart_exometer_core();
+test_newentry_(Config) ->
+ {ok, Info} = start_logger_and_reporter(test_udp, Config),
+ Tab = ets_tab(Info),
+ [] = ets:tab2list(Tab),
+ exometer:new([c], counter, []),
+ %% exometer_report:trigger_interval(test_udp, main),
+ R1 = check_logger_msg(),
+ [{_,{newentry,#exometer_entry{name = [c], type = counter}} = R1}] =
+ ets:tab2list(Tab),
+ ok.
+
+test_subscribe(Config) ->
+ majority(fun test_subscribe_/1, Config).
+
+test_subscribe_({cleanup, _Config}) ->
+ restart_exometer_core();
+test_subscribe_(Config) ->
+ ok = exometer:new([c], counter, []),
+ {ok, _Info} = start_logger_and_reporter(test_udp, Config),
+ exometer_report:subscribe(test_udp, [c], value, main, true),
+ %% exometer_report:trigger_interval(test_udp, main),
+ {subscribe, [{metric, [c]},
+ {datapoint, value} | _]} = check_logger_msg(),
+ ok.
+
+test_subscribe_find(Config) ->
+ majority(fun test_subscribe_find_/1, Config).
+
+test_subscribe_find_({cleanup, _Config}) ->
+ restart_exometer_core();
+test_subscribe_find_(Config) ->
+ ok = exometer:new([c,1], counter, []),
+ ok = exometer:new([c,2], counter, []),
+ {ok, Info} = start_logger_and_reporter(test_udp, Config),
+ exometer_report:subscribe(test_udp, {find,[c,'_']}, value, main, true),
+ {subscribe, [{metric, {find,[c,'_']}},
+ {datapoint, value} | _]} = R1 = check_logger_msg(),
+ exometer_report:trigger_interval(test_udp, main),
+ {report, [{prefix,[]},{metric,[c,1]}|_]} = R2 = check_logger_msg(),
+ {report, [{prefix,[]},{metric,[c,2]}|_]} = R3 = check_logger_msg(),
+ Tab = ets_tab(Info),
+ [{_,R1},{_,R2},{_,R3}] = ets:tab2list(Tab),
+ ok.
+
+test_subscribe_select(Config) ->
+ majority(fun test_subscribe_select_/1, Config).
+
+test_subscribe_select_({cleanup, _Config}) ->
+ restart_exometer_core();
+test_subscribe_select_(Config) ->
+ ok = exometer:new([c,1], counter, []),
+ ok = exometer:new([c,2], counter, []),
+ ok = exometer:new([c,3], counter, []),
+ {ok, Info} = start_logger_and_reporter(test_udp, Config),
+ exometer_report:subscribe(
+ test_udp,
+ {select,[{ {[c,'$1'],'_','_'},[{'<','$1',3}], ['$_'] }]},
+ value, main, true),
+ {subscribe, [{metric, {select,_}},
+ {datapoint, value} | _]} = R1 = check_logger_msg(),
+ exometer_report:trigger_interval(test_udp, main),
+ {report, [{prefix,[]},{metric,[c,1]}|_]} = R2 = check_logger_msg(),
+ {report, [{prefix,[]},{metric,[c,2]}|_]} = R3 = check_logger_msg(),
+ Tab = ets_tab(Info),
+ [{_,R1},{_,R2},{_,R3}] = ets:tab2list(Tab),
+ ok.
+
+test_logger_flow_control(Config) ->
+ test_subscribe_find([{input_port_options, [{active, false}]}|Config]).
+
+test_logger_flow_control_2(Config) ->
+ test_subscribe_find([{input_port_options, [{active, once}]}|Config]).
+
+start_logger_and_reporter(Reporter, Config) ->
+ Port = get_port(Config),
+ IPO = ensure_reuse(config(input_port_options, Config, [])),
+ ct:log("IPO = ~p~n", [IPO]),
+ Res = exometer_report_logger:new(
+ [{id, ?MODULE},
+ {input, [{mode, plugin},
+ {module, exometer_test_udp_reporter},
+ {state, {Port, IPO}}]},
+ {output, [{mode, plugin},
+ {module, exometer_test_udp_reporter}]},
+ {output, [{mode, ets}]},
+ {output, [{mode, plugin},
+ {module, ?MODULE},
+ {state, self()}]}]),
+ ct:log("Logger start: ~p~n", [Res]),
+ Info = exometer_report_logger:info(),
+ ct:log("Logger Info = ~p~n", [Info]),
+ ok = exometer_report:add_reporter(
+ Reporter,
+ [{module, exometer_test_udp_reporter},
+ {hostname, "localhost"},
+ {port, Port},
+ {intervals, [{main, manual}]}]),
+ {ok, Info}.
+
+ensure_reuse(Opts) ->
+ case lists:keyfind(reuseaddr, 1, Opts) of
+ {_, _} ->
+ Opts;
+ false ->
+ [{reuseaddr, true}|Opts]
+ end.
+
+check_logger_msg() ->
+ receive
+ {logger_got, Data} ->
+ ct:log("logger_got: ~p~n", [Data]),
+ Data
+ after 5000 ->
+ error(logger_ack_timeout)
+ end.
+
+ets_tab(Info) ->
+ [T] = [tree_opt([output,ets,tab], I) || {_,[{id,?MODULE}|I]} <- Info],
+ T.
+
+get_port(Config) ->
+ config(port, Config, ?DEFAULT_PORT).
+
+config(Key, Config, Default) ->
+ case ?config(Key, Config) of
+ undefined ->
+ Default;
+ Value ->
+ Value
+ end.
+
+tree_opt([H|T], L) when is_list(L) ->
+ case lists:keyfind(H, 1, L) of
+ {_, Val} ->
+ case T of
+ [] -> Val;
+ [_|_] ->
+ tree_opt(T, Val)
+ end;
+ false ->
+ undefined
+ end;
+tree_opt([], _) ->
+ undefined.
+
+
+logger_init_output(Pid) ->
+ {ok, Pid}.
+
+logger_handle_data(Data, Pid) ->
+ Pid ! {logger_got, Data},
+ {Data, Pid}.
+
+restart_exometer_core() ->
+ application:stop(exometer_core),
+ application:start(exometer_core).
diff --git a/test/exometer_test_udp_reporter.erl b/test/exometer_test_udp_reporter.erl
new file mode 100644
index 0000000..9a65d5f
--- /dev/null
+++ b/test/exometer_test_udp_reporter.erl
@@ -0,0 +1,158 @@
+%% -------------------------------------------------------------------
+%%
+%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%
+%% -------------------------------------------------------------------
+-module(exometer_test_udp_reporter).
+
+-behaviour(exometer_report).
+-export(
+ [
+ exometer_init/1,
+ exometer_info/2,
+ exometer_cast/2,
+ exometer_call/3,
+ exometer_report/5,
+ exometer_subscribe/5,
+ exometer_unsubscribe/4,
+ exometer_newentry/2,
+ exometer_setopts/4,
+ exometer_terminate/2
+ ]).
+
+-behaviour(exometer_report_logger).
+-export([
+ logger_init_input/1,
+ logger_init_output/1,
+ logger_handle_data/2
+ ]).
+
+-import(exometer_util, [get_opt/2, get_opt/3]).
+
+-include_lib("kernel/include/inet.hrl").
+-record(st, {socket, address, port, type_map, prefix = []}).
+
+-define(DEFAULT_HOST, "localhost").
+
+exometer_init(Opts) ->
+ Port = get_opt(port, Opts),
+ {ok, Host} = inet:gethostbyname(get_opt(hostname, Opts, ?DEFAULT_HOST)),
+ [Addr|_] = Host#hostent.h_addr_list,
+ AddrType = Host#hostent.h_addrtype,
+ TypeMap = get_opt(type_map, Opts, []),
+ Prefix = get_opt(prefix, Opts, []),
+ case gen_udp:open(0, [AddrType]) of
+ {ok, Socket} ->
+ {ok, #st{socket = Socket, address = Addr, port = Port,
+ type_map = TypeMap, prefix = Prefix}};
+ {error, _} = Error ->
+ Error
+ end.
+
+exometer_report(Metric, DataPoint, Extra, Value, #st{type_map = TypeMap,
+ prefix = Pfx} = St) ->
+ RptType = exometer_util:report_type({Metric,DataPoint}, Extra, TypeMap),
+ ok = send({report, [{prefix, Pfx},
+ {metric, Metric},
+ {datapoint, DataPoint},
+ {extra, Extra},
+ {report_type, RptType},
+ {value, Value}]}, St),
+ {ok, St}.
+
+exometer_subscribe(Metric, DataPoint, Extra, Interval, St) ->
+ ok = send({subscribe, [{metric, Metric},
+ {datapoint, DataPoint},
+ {extra, Extra},
+ {interval, Interval}]}, St),
+ {ok, St}.
+
+exometer_unsubscribe(Metric, DataPoint, Extra, St) ->
+ ok = send({unsubscribe, [{metric, Metric},
+ {datapoint, DataPoint},
+ {extra, Extra}]}, St),
+ {ok, St}.
+
+exometer_call(Req, From, St) ->
+ ok = send({call, Req, From}, St),
+ {reply, {test_reply, ok}, St}.
+
+exometer_cast(Cast, St) ->
+ ok = send({cast, Cast}, St),
+ {ok, St}.
+
+exometer_info(I, St) ->
+ ok = send({info, I}, St),
+ {ok, St}.
+
+exometer_newentry(E, St) ->
+ ok = send({newentry, E}, St),
+ {ok, St}.
+
+exometer_setopts(Metric, Opts, Status, St) ->
+ ok = send({setopts, [{metric, Metric},
+ {options, Opts},
+ {status, Status}]}, St),
+ {ok, St}.
+
+exometer_terminate(_, _) ->
+ ok.
+
+send(Term, #st{socket = Socket, address = Address, port = Port}) ->
+ gen_udp:send(Socket, Address, Port, term_to_binary(Term, [compressed])).
+
+
+logger_init_input({Port, Opts}) ->
+ Parent = self(),
+ {Pid,_} = spawn_monitor(
+ fun() ->
+ erlang:monitor(process, Parent),
+ {ok, Socket} = gen_udp:open(Port, [binary|Opts]),
+ check_active(Socket, Parent),
+ input_loop(Socket, Parent)
+ end),
+ {ok, Pid}.
+
+logger_init_output(_) ->
+ {ok, []}.
+
+logger_handle_data(Data, St) ->
+ {binary_to_term(iolist_to_binary([Data])), St}.
+
+input_loop(Socket, Parent) ->
+ case receive
+ {udp, Socket, _Host, _Port, Data} ->
+ Parent ! {plugin, self(), Data};
+ {udp_passive, Socket} ->
+ Parent ! {plugin_passive, self()}, check;
+ {plugin_active, Active} ->
+ ct:log("{plugin_active, ~p}", [Active]),
+ inet:setopts(Socket, [{active, Active}]);
+ {'DOWN', _, process, Parent, _} ->
+ exit(normal)
+ after 5000 ->
+ io:fwrite(user, "input_loop timeout~n", [])
+ end of
+ check ->
+ check_active(Socket, Parent);
+ _ ->
+ ok
+ end,
+ input_loop(Socket, Parent).
+
+check_active(Socket, Parent) ->
+ case inet:getopts(Socket, [active]) of
+ {ok, [{active, A}]} ->
+ case A of
+ _ when A==0; A==false; A==once ->
+ Parent ! {plugin_passive, self()};
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
diff --git a/test/exometer_test_util.erl b/test/exometer_test_util.erl
new file mode 100644
index 0000000..fdc57cb
--- /dev/null
+++ b/test/exometer_test_util.erl
@@ -0,0 +1,80 @@
+-module(exometer_test_util).
+
+-export([ensure_all_started/1,
+ majority/2,
+ majority/3]).
+
+-define(MAJORITY_COUNT_ENV, "CT_MAJORITY_COUNT").
+-define(DEFAULT_MAJORITY_COUNT, 5).
+
+%% This implementation is originally from Basho's Webmachine. On
+%% older versions of Erlang, we don't have
+%% application:ensure_all_started, so we use this wrapper function to
+%% either use the native implementation or our own version, depending
+%% on what's available.
+-spec ensure_all_started(atom()) -> {ok, [atom()]} | {error, term()}.
+ensure_all_started(App) ->
+ case erlang:function_exported(application, ensure_all_started, 1) of
+ true ->
+ application:ensure_all_started(App);
+ false ->
+ ensure_all_started(App, [])
+ end.
+
+%% This implementation is originally from Basho's
+%% Webmachine. Reimplementation of ensure_all_started. NOTE this does
+%% not behave the same as the native version in all cases, but as a
+%% quick hack it works well enough for our purposes. Eventually I
+%% assume we'll drop support for older versions of Erlang and this can
+%% be eliminated.
+ensure_all_started(App, Apps0) ->
+ case application:start(App) of
+ ok ->
+ {ok, lists:reverse([App | Apps0])};
+ {error,{already_started,App}} ->
+ {ok, lists:reverse(Apps0)};
+ {error,{not_started,BaseApp}} ->
+ {ok, Apps} = ensure_all_started(BaseApp, Apps0),
+ ensure_all_started(App, [BaseApp|Apps])
+ end.
+
+majority(F, Cfg) ->
+ case os:getenv(?MAJORITY_COUNT_ENV) of
+ false ->
+ majority(?DEFAULT_MAJORITY_COUNT, F, Cfg);
+ Count ->
+ case catch erlang:list_to_integer(Count) of
+ C when is_integer(C) ->
+ majority(C, F, Cfg);
+ _ ->
+ ct:pal("Invalid value for '~s' given", [?MAJORITY_COUNT_ENV]),
+ majority(?DEFAULT_MAJORITY_COUNT, F, Cfg)
+ end
+ end.
+
+%% Run test N times. Success if a majority of the tests succeed.
+%% Cleanup between runs done by calling F({cleanup, Config})
+%% Returns 'ok' or {error, Info}.
+%%
+majority(N, F, Cfg) ->
+ majority(N, F, Cfg, []).
+
+majority(0, _, _, Hist) ->
+ Failed = length([1 || {caught,_,_} <- Hist]),
+ LogMsg = lists:flatten(io_lib:format("majority: Failed = ~p, Hist = ~p", [Failed, Hist])),
+ ct:pal(LogMsg),
+ case {Failed, length(Hist)} of
+ {Lf, L} when Lf >= L div 2 ->
+ ct:fail({error, {too_many_failures, Hist}});
+ _ ->
+ {comment, LogMsg}
+ end;
+majority(N, F, Cfg, Hist) when N > 0 ->
+ Res = try F(Cfg)
+ catch
+ C:R ->
+ {caught, C, R}
+ after
+ F({cleanup, Cfg})
+ end,
+ majority(N-1, F, Cfg, [Res|Hist]).