changed
CHANGELOG.md
|
@@ -7,6 +7,51 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
> Major version zero (0.y.z) is for initial development. Anything MAY change at
|
8
8
|
any time. The public API SHOULD NOT be considered stable.
|
9
9
|
|
10
|
+ ## [v0.5.0] - 2022-05-32
|
11
|
+
|
12
|
+ Breaking changes for three functions in the `Mobius.Exports` module:
|
13
|
+
|
14
|
+ 1. `Mobius.Exports.series/4`
|
15
|
+ 1. `Mobius.Exports.metrics/4`
|
16
|
+ 1. `Mobius.Exports.plot/4`
|
17
|
+
|
18
|
+ If you are now directly calling these functions in code you're safe to upgrade.
|
19
|
+
|
20
|
+ The first two use to return either `{:ok, results}` or `{:error, reason}` but
|
21
|
+ now they will only return their result. For `Mobius.Exports.series/4` the return
|
22
|
+ value is now `[integer()]` and for `Mobius.Exports.metrics/4` the return type is
|
23
|
+ now `[Mobius.metric()]`. `Mobius.Exports.plot/4` still returns `:ok` on success,
|
24
|
+ but can now return `{:error, UnsupportedMetricError.t()}`.
|
25
|
+
|
26
|
+ ### Changed
|
27
|
+
|
28
|
+ * `Mobius.Exports.series/4` return type was
|
29
|
+ `{:ok, [integer()]} | {:error, UnsupportedMetricError.t()}` and now is
|
30
|
+ `[integer()]`.
|
31
|
+ * `Mobius.Exports.metrics/4` return type was
|
32
|
+ `{:ok, [Mobius.metric()]} | {:error, UnsupportedMetricError.t()}` and now is
|
33
|
+ `[Mobius.metric()]`
|
34
|
+ * `Mobius.Exports.plot/4` was just `:ok` but now is
|
35
|
+ `:ok | {:error, UnsupportedMetricError.t()}`
|
36
|
+
|
37
|
+ ### Added
|
38
|
+
|
39
|
+ * `Mobius.RemoteReporter` behaviour to allow for reporting metrics to a remote
|
40
|
+ server.
|
41
|
+ * Add `:remote_reporter` and `:remote_report_interval` options to the
|
42
|
+ `Mobius.arg()` type.
|
43
|
+ * Super for specify which summery metric you want to export. (@ewildgoose)
|
44
|
+ * Support for summary metrics types in some exports. (@ewildgoose)
|
45
|
+ * Add standard deviation calculation to the summary metric type. (@ewildgoose)
|
46
|
+ * New `Mobius.Exports.export_metric_type()` that allows for specifying the
|
47
|
+ summary metric type.
|
48
|
+
|
49
|
+ ### Misc
|
50
|
+
|
51
|
+ * Update `ex_doc` to `v.0.28.4`
|
52
|
+ * Update `telemetry` to `v1.1.0`
|
53
|
+ * Fix up typos (@kianmeng)
|
54
|
+
|
10
55
|
## [v0.4.0] - 2022-03-25
|
11
56
|
|
12
57
|
### Changed
|
|
@@ -60,96 +105,97 @@ basic maintenance like dependency updates and documentation improvements
|
60
105
|
|
61
106
|
### Added
|
62
107
|
|
63
|
- - Create, save, and extract tar files that contain metric data, see
|
108
|
+ * Create, save, and extract tar files that contain metric data, see
|
64
109
|
`Mobius.Bundles` and `Mobius.make_bundle/2` for more information.
|
65
|
- - `Mobius.filter_metrics/3` to filter for desired metrics to enable the
|
110
|
+ * `Mobius.filter_metrics/3` to filter for desired metrics to enable the
|
66
111
|
metrics to be consumed externally (@ewildgoose)
|
67
|
- - `Mobius.save/1` to manually save the state of the metric data for Mobius
|
112
|
+ * `Mobius.save/1` to manually save the state of the metric data for Mobius
|
68
113
|
(@ewildgoose)
|
69
|
- - `:autosave_interval` option to Mobius to enable a saving data at the given
|
114
|
+ * `:autosave_interval` option to Mobius to enable a saving data at the given
|
70
115
|
interval (@ewildgoose)
|
71
116
|
|
72
117
|
### Fixes
|
73
118
|
|
74
|
- - Unit conversion not working correctly (@ewildgoose)
|
75
|
- - Error handling for when the `:persistence_path` is missing (@ewildgoose)
|
76
|
- - Error handling when there is no data to plot (@ewildgoose)
|
77
|
- - Crash when plotting an array of identical values (@ewildgoose)
|
78
|
- - Correct off by one error when plotting (@ewildgoose)
|
119
|
+ * Unit conversion not working correctly (@ewildgoose)
|
120
|
+ * Error handling for when the `:persistence_path` is missing (@ewildgoose)
|
121
|
+ * Error handling when there is no data to plot (@ewildgoose)
|
122
|
+ * Crash when plotting an array of identical values (@ewildgoose)
|
123
|
+ * Correct off by one error when plotting (@ewildgoose)
|
79
124
|
|
80
125
|
## [v0.3.6] - 2022-01-25
|
81
126
|
|
82
127
|
### Added
|
83
128
|
|
84
|
- - Support for `Telemetry.Metrics.Summary` metric type
|
129
|
+ * Support for `Telemetry.Metrics.Summary` metric type
|
85
130
|
|
86
131
|
## [v0.3.5] - 2021-12-2
|
87
132
|
|
88
133
|
### Fixes
|
89
134
|
|
90
|
- - Fix crash when initializing metrics table when the ETS file cannot be read (@jfcloutier)
|
135
|
+ * Fix crash when initializing metrics table when the ETS file cannot be read (@jfcloutier)
|
91
136
|
|
92
137
|
## [v0.3.4] - 2021-11-15
|
93
138
|
|
94
139
|
### Fixes
|
95
140
|
|
96
|
- - Fix crash when a history file is unreadable during initialization (@mdwaud)
|
141
|
+ * Fix crash when a history file is unreadable during initialization (@mdwaud)
|
97
142
|
|
98
143
|
## [v0.3.3] - 2021-10-20
|
99
144
|
|
100
145
|
### Fixes
|
101
146
|
|
102
|
- - Not able to pass a path for persistence that contains non-existing sub
|
147
|
+ * Not able to pass a path for persistence that contains non-existing sub
|
103
148
|
directories. Thank you [LostKobrakai](https://github.com/LostKobrakai).
|
104
149
|
|
105
150
|
## [v0.3.2] - 2021-09-22
|
106
151
|
|
107
152
|
### Added
|
108
153
|
|
109
|
- - Support for `Telemetry.Metrics.Sum` type
|
110
|
- - Support for filtering CSV records by type with `:type` option
|
154
|
+ * Support for `Telemetry.Metrics.Sum` type
|
155
|
+ * Support for filtering CSV records by type with `:type` option
|
111
156
|
|
112
157
|
## [v0.3.1] - 2021-09-08
|
113
158
|
|
114
159
|
### Added
|
115
160
|
|
116
|
- - Plot over the last `x` seconds via the `:last` plot option
|
117
|
- - Plot from an absolute time via the `:from` plot option
|
118
|
- - Plot to an absolute time via the `:to` plot option
|
119
|
- - Print or save metric time series via `Mobius.to_csv/3`
|
120
|
- - Remove tracking a metric by dropping it from the metric list passed to Mobius
|
161
|
+ * Plot over the last `x` seconds via the `:last` plot option
|
162
|
+ * Plot from an absolute time via the `:from` plot option
|
163
|
+ * Plot to an absolute time via the `:to` plot option
|
164
|
+ * Print or save metric time series via `Mobius.to_csv/3`
|
165
|
+ * Remove tracking a metric by dropping it from the metric list passed to Mobius
|
121
166
|
|
122
167
|
### Changed
|
123
168
|
|
124
|
- - `Mobius.plot/3` will only show the last 3 minutes of data by default
|
169
|
+ * `Mobius.plot/3` will only show the last 3 minutes of data by default
|
125
170
|
|
126
171
|
## [v0.3.0] - 2021-8-19
|
127
172
|
|
128
173
|
### Changed
|
129
174
|
|
130
|
- - Deleted `Mobius.Charts` module. The functions in this module are now located
|
175
|
+ * Deleted `Mobius.Charts` module. The functions in this module are now located
|
131
176
|
in the `Mobius` module.
|
132
177
|
|
133
178
|
### Removed
|
134
179
|
|
135
|
- - Support for specifying resolutions.
|
180
|
+ * Support for specifying resolutions.
|
136
181
|
|
137
182
|
## [v0.2.0] - 2021-8-03
|
138
183
|
|
139
184
|
### Added
|
140
185
|
|
141
|
- - `Mobius.Charts` module
|
142
|
- - Persistence of historical information on graceful shutdown
|
143
|
- - Ability to specify time resolutions for plots
|
186
|
+ * `Mobius.Charts` module
|
187
|
+ * Persistence of historical information on graceful shutdown
|
188
|
+ * Ability to specify time resolutions for plots
|
144
189
|
|
145
190
|
### Changed
|
146
191
|
|
147
|
- - Move `Moblus.plot/0` and `Mobius.info/0` to `Mobius.Charts` module
|
192
|
+ * Move `Moblus.plot/0` and `Mobius.info/0` to `Mobius.Charts` module
|
148
193
|
|
149
194
|
## v0.1.0 - 2021-7-16
|
150
195
|
|
151
196
|
Initial release!
|
152
197
|
|
198
|
+ [v0.5.0]: https://github.com/mattludwigs/mobius/compare/v0.4.0...v0.5.0
|
153
199
|
[v0.4.0]: https://github.com/mattludwigs/mobius/compare/v0.3.7...v0.4.0
|
154
200
|
[v0.3.7]: https://github.com/mattludwigs/mobius/compare/v0.3.6...v0.3.7
|
155
201
|
[v0.3.6]: https://github.com/mattludwigs/mobius/compare/v0.3.5...v0.3.6
|
changed
README.md
|
@@ -1,6 +1,6 @@
|
1
1
|
# Mobius
|
2
2
|
|
3
|
- [![CircleCI](https://circleci.com/gh/mattludwigs/mobius/tree/main.svg?style=svg)](https://circleci.com/gh/mattludwigs/mobius/tree/main)
|
3
|
+ [![CircleCI](https://circleci.com/gh/mobius-home/mobius/tree/main.svg?style=svg)](https://circleci.com/gh/mobius-home/mobius/tree/main)
|
4
4
|
|
5
5
|
![Mobius](assets/mobius-name.png)
|
6
6
|
|
|
@@ -11,7 +11,7 @@ Library for localized telemetry metrics
|
11
11
|
```elixir
|
12
12
|
def deps do
|
13
13
|
[
|
14
|
- {:mobius, "~> 0.4.0"}
|
14
|
+ {:mobius, "~> 0.5.0"}
|
15
15
|
]
|
16
16
|
end
|
17
17
|
```
|
|
@@ -123,7 +123,6 @@ def start(_type, _args) do
|
123
123
|
end
|
124
124
|
```
|
125
125
|
|
126
|
-
|
127
126
|
### Exporting data
|
128
127
|
|
129
128
|
The `Mobius.Exports` module provide functions for exporting the data in a couple
|
|
@@ -151,3 +150,11 @@ most useful for preparing metrics to send off to another system. To parse
|
151
150
|
the binary format you can use `Mobius.Exports.parse_mbf/1`.
|
152
151
|
|
153
152
|
For each of these you can see the `Mobius.Exports` module for more details.
|
153
|
+
|
154
|
+ ### Report metrics to a remote server
|
155
|
+
|
156
|
+ Mobius allows sending metrics to a remote server. You can do this by passing the
|
157
|
+ `:remote_reporter` option to Mobius. This is a module that implements the
|
158
|
+ `Mobius.RemoteReporter` behaviour. Optionally, you can pass the
|
159
|
+ `:remote_report_interval` option to specify how often to report metrics, by
|
160
|
+ default this is every 1 minute.
|
changed
hex_metadata.config
|
@@ -3,15 +3,19 @@
|
3
3
|
{<<"description">>,<<"Local metrics library">>}.
|
4
4
|
{<<"elixir">>,<<"~> 1.11">>}.
|
5
5
|
{<<"files">>,
|
6
|
- [<<"lib">>,<<"lib/mobius.ex">>,<<"lib/mobius">>,<<"lib/mobius/events.ex">>,
|
7
|
- <<"lib/mobius/registry.ex">>,<<"lib/mobius/asciichart.ex">>,
|
8
|
- <<"lib/mobius/rrd.ex">>,<<"lib/mobius/metrics_table">>,
|
9
|
- <<"lib/mobius/metrics_table/monitor.ex">>,<<"lib/mobius/exports.ex">>,
|
10
|
- <<"lib/mobius/scraper.ex">>,<<"lib/mobius/exports">>,
|
6
|
+ [<<"lib">>,<<"lib/mobius.ex">>,<<"lib/mobius">>,<<"lib/mobius/scraper.ex">>,
|
7
|
+ <<"lib/mobius/exports.ex">>,<<"lib/mobius/metrics_table">>,
|
8
|
+ <<"lib/mobius/metrics_table/monitor.ex">>,<<"lib/mobius/exceptions.ex">>,
|
9
|
+ <<"lib/mobius/remote_reporter.ex">>,<<"lib/mobius/auto_save.ex">>,
|
10
|
+ <<"lib/mobius/remote_reporter_server.ex">>,
|
11
|
+ <<"lib/mobius/remote_reporters">>,
|
12
|
+ <<"lib/mobius/remote_reporters/logger_reporter.ex">>,
|
13
|
+ <<"lib/mobius/exports">>,<<"lib/mobius/exports/metrics.ex">>,
|
14
|
+ <<"lib/mobius/exports/csv.ex">>,
|
11
15
|
<<"lib/mobius/exports/mobius_binary_format.ex">>,
|
12
|
- <<"lib/mobius/exports/metrics.ex">>,<<"lib/mobius/exports/csv.ex">>,
|
13
|
- <<"lib/mobius/metrics_table.ex">>,<<"lib/mobius/auto_save.ex">>,
|
14
|
- <<"lib/mobius/summary.ex">>,<<"lib/mobius/exceptions.ex">>,
|
16
|
+ <<"lib/mobius/asciichart.ex">>,<<"lib/mobius/rrd.ex">>,
|
17
|
+ <<"lib/mobius/events.ex">>,<<"lib/mobius/metrics_table.ex">>,
|
18
|
+ <<"lib/mobius/summary.ex">>,<<"lib/mobius/registry.ex">>,
|
15
19
|
<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"CHANGELOG.md">>]}.
|
16
20
|
{<<"licenses">>,[<<"Apache-2.0">>]}.
|
17
21
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/mattludwigs/mobius">>}]}.
|
|
@@ -32,4 +36,4 @@
|
32
36
|
{<<"optional">>,false},
|
33
37
|
{<<"repository">>,<<"hexpm">>},
|
34
38
|
{<<"requirement">>,<<"~> 0.4.0">>}]]}.
|
35
|
- {<<"version">>,<<"0.4.0">>}.
|
39
|
+ {<<"version">>,<<"0.5.0">>}.
|
changed
lib/mobius.ex
|
@@ -5,7 +5,7 @@ defmodule Mobius do
|
5
5
|
|
6
6
|
use Supervisor
|
7
7
|
|
8
|
- alias Mobius.{MetricsTable, Scraper, Summary}
|
8
|
+ alias Mobius.{MetricsTable, RemoteReporter, Scraper, Summary}
|
9
9
|
|
10
10
|
alias Telemetry.Metrics
|
11
11
|
|
|
@@ -29,6 +29,8 @@ defmodule Mobius do
|
29
29
|
| {:metrics, [Metrics.t()]}
|
30
30
|
| {:persistence_dir, binary()}
|
31
31
|
| {:database, Mobius.RRD.t()}
|
32
|
+ | {:remote_reporter, RemoteReporter.t() | {RemoteReporter.t(), term()}}
|
33
|
+ | {:remote_report_interval, non_neg_integer()}
|
32
34
|
|
33
35
|
@typedoc """
|
34
36
|
The name of the Mobius instance
|
|
@@ -69,7 +71,11 @@ defmodule Mobius do
|
69
71
|
Start Mobius
|
70
72
|
"""
|
71
73
|
def start_link(args) do
|
72
|
- Supervisor.start_link(__MODULE__, ensure_args(args), name: __MODULE__.Supervisor)
|
74
|
+ Supervisor.start_link(__MODULE__, ensure_args(args), name: name(args[:mobius_instance]))
|
75
|
+ end
|
76
|
+
|
77
|
+ defp name(instance) do
|
78
|
+ Module.concat(__MODULE__.Supervisor, instance)
|
73
79
|
end
|
74
80
|
|
75
81
|
@impl Supervisor
|
|
@@ -92,6 +98,7 @@ defmodule Mobius do
|
92
98
|
{Mobius.Scraper, args}
|
93
99
|
]
|
94
100
|
|> maybe_enable_autosave(args)
|
101
|
+ |> maybe_start_remote_reporter(args)
|
95
102
|
|
96
103
|
Supervisor.init(children, strategy: :one_for_one)
|
97
104
|
|
|
@@ -128,6 +135,27 @@ defmodule Mobius do
|
128
135
|
end
|
129
136
|
end
|
130
137
|
|
138
|
+ defp maybe_start_remote_reporter(children, args) do
|
139
|
+ case args[:remote_reporter] do
|
140
|
+ nil ->
|
141
|
+ children
|
142
|
+
|
143
|
+ _config ->
|
144
|
+ children ++ [{Mobius.RemoteReporterServer, make_remote_report_server_args(args)}]
|
145
|
+ end
|
146
|
+ end
|
147
|
+
|
148
|
+ defp make_remote_report_server_args(mobius_args) do
|
149
|
+ reporter = Keyword.fetch!(mobius_args, :remote_reporter)
|
150
|
+ report_interval = mobius_args[:remote_report_interval]
|
151
|
+
|
152
|
+ [
|
153
|
+ reporter: reporter,
|
154
|
+ report_interval: report_interval,
|
155
|
+ mobius_instance: mobius_args[:mobius_instance]
|
156
|
+ ]
|
157
|
+ end
|
158
|
+
|
131
159
|
@doc """
|
132
160
|
Get the current metric information
|
changed
lib/mobius/exports.ex
|
@@ -15,7 +15,8 @@ defmodule Mobius.Exports do
|
15
15
|
`mbf/1`.
|
16
16
|
"""
|
17
17
|
|
18
|
- alias Mobius.Exports.{MobiusBinaryFormat, UnsupportedMetricError}
|
18
|
+ alias Mobius.Asciichart
|
19
|
+ alias Mobius.Exports.{CSV, Metrics, MobiusBinaryFormat, UnsupportedMetricError}
|
19
20
|
|
20
21
|
@typedoc """
|
21
22
|
Options to use when exporting time series metric data
|
|
@@ -45,6 +46,14 @@ defmodule Mobius.Exports do
|
45
46
|
| {:headers, boolean()}
|
46
47
|
| {:iodevice, IO.device()}
|
47
48
|
|
49
|
+ @typedoc """
|
50
|
+ Metric types that can be exported
|
51
|
+
|
52
|
+ By default you can try to export any `Mobius.metric_type()`, but for then
|
53
|
+ summary metric type you can specify which summary type you want to export.
|
54
|
+ """
|
55
|
+ @type export_metric_type() :: Mobius.metric_type() | {:summary, atom()}
|
56
|
+
|
48
57
|
@doc """
|
49
58
|
Generate a CSV for the metric
|
50
59
|
|
|
@@ -62,32 +71,28 @@ defmodule Mobius.Exports do
|
62
71
|
:ok = Mobius.Exports.csv("vm.memory.total", :last_value, %{}, iodevice: file)
|
63
72
|
```
|
64
73
|
"""
|
65
|
- @spec csv(binary(), Mobius.metric_type(), map(), [csv_export_opt()]) ::
|
74
|
+ @spec csv(binary(), export_metric_type(), map(), [csv_export_opt()]) ::
|
66
75
|
:ok | {:ok, binary()} | {:error, UnsupportedMetricError.t()}
|
67
|
- def csv(metric_name, type, tags, opts \\ []) do
|
68
|
- case get_metrics(metric_name, type, tags, opts) do
|
69
|
- {:ok, metrics} ->
|
70
|
- export_opts = build_exporter_opts(metric_name, type, tags, opts)
|
71
|
- Mobius.Exports.CSV.export_metrics(metrics, export_opts)
|
76
|
+ def csv(metric_name, type, tags, opts \\ [])
|
72
77
|
|
73
|
- error ->
|
74
|
- error
|
75
|
- end
|
78
|
+ def csv(_metric_name, :summary, _tags, _opts) do
|
79
|
+ {:error, UnsupportedMetricError.exception(metric_type: :summary)}
|
80
|
+ end
|
81
|
+
|
82
|
+ def csv(metric_name, type, tags, opts) do
|
83
|
+ metrics = get_metrics(metric_name, type, tags, opts)
|
84
|
+ export_opts = build_exporter_opts(metric_name, type, tags, opts)
|
85
|
+ CSV.export_metrics(metrics, export_opts)
|
76
86
|
end
|
77
87
|
|
78
88
|
@doc """
|
79
89
|
Generates a series that contains the value of the metric
|
80
90
|
"""
|
81
|
- @spec series(String.t(), Mobius.metric_type(), map(), [export_opt()]) ::
|
82
|
- {:ok, [integer()]} | {:error, UnsupportedMetricError.t()}
|
91
|
+ @spec series(String.t(), export_metric_type(), map(), [export_opt()]) :: [integer()]
|
83
92
|
def series(metric_name, type, tags, opts \\ []) do
|
84
|
- case get_metrics(metric_name, type, tags, opts) do
|
85
|
- {:ok, metrics} ->
|
86
|
- {:ok, Enum.map(metrics, fn metric -> metric.value end)}
|
87
|
-
|
88
|
- error ->
|
89
|
- error
|
90
|
- end
|
93
|
+ metric_name
|
94
|
+ |> get_metrics(type, tags, opts)
|
95
|
+ |> Enum.map(& &1.value)
|
91
96
|
end
|
92
97
|
|
93
98
|
@doc """
|
|
@@ -126,17 +131,19 @@ defmodule Mobius.Exports do
|
126
131
|
```elixir
|
127
132
|
Mobius.Exports.metrics("vm.memory.total", :last_value, %{}, last: {2, :hour})
|
128
133
|
```
|
134
|
+
|
135
|
+ Retrieving summary data can be performed by specifying the type: :summary - however, this returns
|
136
|
+ value data in the form of a map, which cannot be plotted or csv exported. To reduce the output to
|
137
|
+ a single metric value, use the form: {:summary, :summary_metric}
|
138
|
+
|
139
|
+ ```elixir
|
140
|
+ Mobius.Exports.metrics("vm.memory.total", {:summary, :average}, %{}, last: {2, :hour})
|
141
|
+ ```
|
129
142
|
"""
|
130
143
|
@spec metrics(Mobius.metric_name(), Mobius.metric_type(), map(), [export_opt()] | keyword()) ::
|
131
|
- {:ok, [Mobius.metric()]} | {:error, UnsupportedMetricError.t()}
|
132
|
- def metrics(metric_name, type, tags, opts \\ [])
|
133
|
-
|
134
|
- def metrics(_metric_name, :summary, _tags, _opts) do
|
135
|
- {:error, UnsupportedMetricError.exception(metric_type: :summary)}
|
136
|
- end
|
137
|
-
|
138
|
- def metrics(metric_name, type, tags, opts) do
|
139
|
- {:ok, Mobius.Exports.Metrics.export(metric_name, type, tags, opts)}
|
144
|
+ [Mobius.metric()]
|
145
|
+ def metrics(metric_name, type, tags, opts \\ []) do
|
146
|
+ Metrics.export(metric_name, type, tags, opts)
|
140
147
|
end
|
141
148
|
|
142
149
|
defp get_metrics(metric_name, type, tags, opts) do
|
|
@@ -190,28 +197,45 @@ defmodule Mobius.Exports do
|
190
197
|
```elixir
|
191
198
|
Mobius.Export.plot("vm.memory.total", :last_value, %{}, last: {2, :hour})
|
192
199
|
```
|
193
|
- """
|
194
|
- @spec plot(Mobius.metric_name(), Mobius.metric_type(), map(), [export_opt()]) :: :ok
|
195
|
- def plot(metric_name, type, tags \\ %{}, opts \\ []) do
|
196
|
- with {:ok, series} <- series(metric_name, type, tags, opts),
|
197
|
- {:ok, plot} <- Mobius.Asciichart.plot(series, height: 12) do
|
198
|
- chart = [
|
199
|
- "\t\t",
|
200
|
- IO.ANSI.yellow(),
|
201
|
- "Metric Name: ",
|
202
|
- metric_name,
|
203
|
- IO.ANSI.reset(),
|
204
|
- ", ",
|
205
|
- IO.ANSI.cyan(),
|
206
|
- "Tags: #{inspect(tags)}",
|
207
|
- IO.ANSI.reset(),
|
208
|
- "\n\n",
|
209
|
- plot
|
210
|
- ]
|
211
200
|
|
212
|
- IO.puts(chart)
|
213
|
- else
|
214
|
- error -> error
|
201
|
+ Retrieving summary data can be performed by specifying type of the form:
|
202
|
+ {:summary, :summary_metric}
|
203
|
+
|
204
|
+ ```elixir
|
205
|
+ Mobius.Exports.metrics("vm.memory.total", {:summary, :average}, %{}, last: {2, :hour})
|
206
|
+ ```
|
207
|
+ """
|
208
|
+ @spec plot(Mobius.metric_name(), export_metric_type(), map(), [export_opt()]) ::
|
209
|
+ :ok | {:error, UnsupportedMetricError.t()}
|
210
|
+ def plot(metric_name, type, tags \\ %{}, opts \\ [])
|
211
|
+
|
212
|
+ def plot(_metric_name, :summary, _tags, _opts) do
|
213
|
+ {:error, UnsupportedMetricError.exception(metric_type: :summary)}
|
214
|
+ end
|
215
|
+
|
216
|
+ def plot(metric_name, type, tags, opts) do
|
217
|
+ series = series(metric_name, type, tags, opts)
|
218
|
+
|
219
|
+ case Asciichart.plot(series, height: 12) do
|
220
|
+ {:ok, plot} ->
|
221
|
+ chart = [
|
222
|
+ "\t\t",
|
223
|
+ IO.ANSI.yellow(),
|
224
|
+ "Metric Name: ",
|
225
|
+ metric_name,
|
226
|
+ IO.ANSI.reset(),
|
227
|
+ ", ",
|
228
|
+ IO.ANSI.cyan(),
|
229
|
+ "Tags: #{inspect(tags)}",
|
230
|
+ IO.ANSI.reset(),
|
231
|
+ "\n\n",
|
232
|
+ plot
|
233
|
+ ]
|
234
|
+
|
235
|
+ IO.puts(chart)
|
236
|
+
|
237
|
+ error ->
|
238
|
+ error
|
215
239
|
end
|
216
240
|
end
|
changed
lib/mobius/exports/metrics.ex
|
@@ -3,7 +3,7 @@ defmodule Mobius.Exports.Metrics do
|
3
3
|
|
4
4
|
# Module for exporting metrics
|
5
5
|
|
6
|
- alias Mobius.{Exports, Scraper}
|
6
|
+ alias Mobius.{Exports, Scraper, Summary}
|
7
7
|
|
8
8
|
@doc """
|
9
9
|
Export metrics
|
|
@@ -43,7 +43,25 @@ defmodule Mobius.Exports.Metrics do
|
43
43
|
rows
|
44
44
|
end
|
45
45
|
|
46
|
+ defp filter_metrics_for_metric(metrics, metric_name, :summary, tags) do
|
47
|
+ do_filter_metrics_for_metric(metrics, metric_name, :summary, tags)
|
48
|
+ |> Enum.map(fn metric ->
|
49
|
+ %{metric | value: metric.value |> Summary.calculate()}
|
50
|
+ end)
|
51
|
+ end
|
52
|
+
|
53
|
+ defp filter_metrics_for_metric(metrics, metric_name, {:summary, summary_metric}, tags) do
|
54
|
+ do_filter_metrics_for_metric(metrics, metric_name, :summary, tags)
|
55
|
+ |> Enum.map(fn metric ->
|
56
|
+ %{metric | value: metric.value |> Summary.calculate() |> Map.get(summary_metric)}
|
57
|
+ end)
|
58
|
+ end
|
59
|
+
|
46
60
|
defp filter_metrics_for_metric(metrics, metric_name, type, tags) do
|
61
|
+ do_filter_metrics_for_metric(metrics, metric_name, type, tags)
|
62
|
+ end
|
63
|
+
|
64
|
+ defp do_filter_metrics_for_metric(metrics, metric_name, type, tags) do
|
47
65
|
Enum.filter(metrics, fn metric ->
|
48
66
|
metric_name == metric.name && match?(^tags, metric.tags) && type == metric.type
|
49
67
|
end)
|
changed
lib/mobius/exports/mobius_binary_format.ex
|
@@ -1,6 +1,8 @@
|
1
1
|
defmodule Mobius.Exports.MobiusBinaryFormat do
|
2
2
|
@moduledoc false
|
3
3
|
|
4
|
+ alias Mobius.Exports.MBFParseError
|
5
|
+
|
4
6
|
@format_version 1
|
5
7
|
|
6
8
|
@doc """
|
|
@@ -16,22 +18,20 @@ defmodule Mobius.Exports.MobiusBinaryFormat do
|
16
18
|
"""
|
17
19
|
@spec parse(binary()) :: {:ok, [Mobius.metric()]} | {:error, Mobius.Exports.MBFParseError.t()}
|
18
20
|
def parse(<<@format_version, metrics_bin::binary>>) do
|
19
|
- try do
|
20
|
- metrics = :erlang.binary_to_term(metrics_bin)
|
21
|
+ metrics = :erlang.binary_to_term(metrics_bin)
|
21
22
|
|
22
|
- if validate_metrics(metrics) do
|
23
|
- {:ok, metrics}
|
24
|
- else
|
25
|
- {:error, Mobius.Exports.MBFParseError.exception(:invalid_format)}
|
26
|
- end
|
27
|
- rescue
|
28
|
- ArgumentError ->
|
29
|
- {:error, Mobius.Exports.MBFParseError.exception(:corrupt)}
|
23
|
+ if validate_metrics(metrics) do
|
24
|
+ {:ok, metrics}
|
25
|
+ else
|
26
|
+ {:error, MBFParseError.exception(:invalid_format)}
|
30
27
|
end
|
28
|
+ rescue
|
29
|
+ ArgumentError ->
|
30
|
+ {:error, MBFParseError.exception(:corrupt)}
|
31
31
|
end
|
32
32
|
|
33
33
|
def parse(_other) do
|
34
|
- {:error, Mobius.Exports.MBFParseError.exception(:invalid_format)}
|
34
|
+ {:error, MBFParseError.exception(:invalid_format)}
|
35
35
|
end
|
36
36
|
|
37
37
|
defp validate_metrics(metrics) when is_list(metrics) do
|
added
lib/mobius/remote_reporter.ex
|
@@ -0,0 +1,85 @@
|
1
|
+ defmodule Mobius.RemoteReporter do
|
2
|
+ @moduledoc """
|
3
|
+ Behaviour for modules that report mobius metrics to a remote server
|
4
|
+
|
5
|
+ A remote reporter allows mobius to report metrics to a remote server at some
|
6
|
+ configured interval.
|
7
|
+
|
8
|
+ Say we have a remote reporter who needs an API token to communicate with a
|
9
|
+ remote server. The implementation could look like:
|
10
|
+
|
11
|
+ ```elixir
|
12
|
+ defmodule MyRemoteReporter do
|
13
|
+ @behaviour Mobius.RemoteReporter
|
14
|
+
|
15
|
+ @impl Mobius.RemoteReporter
|
16
|
+ def init(args) do
|
17
|
+ token = Keyword.fetch!(args, :token)
|
18
|
+
|
19
|
+ {:ok, %{token: token}}
|
20
|
+ end
|
21
|
+
|
22
|
+ @impl Mobius.RemoteReporter
|
23
|
+ def handle_metrics(metrics, state) do
|
24
|
+ # ...send metrics
|
25
|
+ {:noreply, state}
|
26
|
+ end
|
27
|
+ end
|
28
|
+ ```
|
29
|
+
|
30
|
+ To use this implementation configure Mobius to report metrics every minute
|
31
|
+ (the default).
|
32
|
+
|
33
|
+ ```elixir
|
34
|
+ def start(_type, _args) do
|
35
|
+ metrics = [
|
36
|
+ Metrics.last_value("my.telemetry.event"),
|
37
|
+ ]
|
38
|
+
|
39
|
+ children = [
|
40
|
+ # ... other children ....
|
41
|
+ {Mobius,
|
42
|
+ metrics: metrics,
|
43
|
+ remote_reporter:
|
44
|
+ {MyRemoteReporter,
|
45
|
+ token: "s3curity"},
|
46
|
+ remote_report_interval: 60_000}
|
47
|
+ # ... other children ....
|
48
|
+ ]
|
49
|
+
|
50
|
+ opts = [strategy: :one_for_one, name: MyApp.Supervisor]
|
51
|
+ Supervisor.start_link(children, opts)
|
52
|
+ end
|
53
|
+ ```
|
54
|
+
|
55
|
+ If you are okay with the one minute reports you do not have to provide the
|
56
|
+ `:remote_report_interval` option.
|
57
|
+ """
|
58
|
+
|
59
|
+ @typedoc """
|
60
|
+ Module that implements the `Mobius.RemoteReporter` behaviour
|
61
|
+ """
|
62
|
+ @type t() :: module()
|
63
|
+
|
64
|
+ @doc """
|
65
|
+ Initialize the reporter
|
66
|
+
|
67
|
+ This callback will receive any configured arguments. These are specific to the
|
68
|
+ reporter implementation but some options might be tokens, host names, ports,
|
69
|
+ etc.
|
70
|
+ """
|
71
|
+ @callback init(opts :: term()) :: {:ok, state :: term()}
|
72
|
+
|
73
|
+ @doc """
|
74
|
+ Handle when metrics are ready to be reported
|
75
|
+
|
76
|
+ This callback will receive a list of metrics and can preform any operation it
|
77
|
+ is designed to do.
|
78
|
+
|
79
|
+ The report will only receive the metric from the last report time until the
|
80
|
+ current time, so the implementation does not need to worry about querying
|
81
|
+ only the newest metrics.
|
82
|
+ """
|
83
|
+ @callback handle_metrics([Mobius.metric()], state :: term()) ::
|
84
|
+ {:noreply, state :: term()}
|
85
|
+ end
|
added
lib/mobius/remote_reporter_server.ex
|
@@ -0,0 +1,91 @@
|
1
|
+ defmodule Mobius.RemoteReporterServer do
|
2
|
+ @moduledoc false
|
3
|
+
|
4
|
+ use GenServer
|
5
|
+
|
6
|
+ require Logger
|
7
|
+
|
8
|
+ alias Mobius.{RemoteReporter, Scraper}
|
9
|
+
|
10
|
+ @typedoc """
|
11
|
+ Arguments to the client server
|
12
|
+ """
|
13
|
+ @type arg() ::
|
14
|
+ {:reporter, RemoteReporter.t() | {RemoteReporter.t(), term()}}
|
15
|
+ | {:report_interval, non_neg_integer()}
|
16
|
+ | {:mobius_instance, Mobius.instance()}
|
17
|
+
|
18
|
+ @doc """
|
19
|
+ Start the client server
|
20
|
+ """
|
21
|
+ @spec start_link([arg()]) :: GenServer.on_start()
|
22
|
+ def start_link(args) do
|
23
|
+ instance = Keyword.fetch!(args, :mobius_instance)
|
24
|
+
|
25
|
+ GenServer.start_link(__MODULE__, args, name: name(instance))
|
26
|
+ end
|
27
|
+
|
28
|
+ defp name(mobius_instance) do
|
29
|
+ Module.concat(__MODULE__, mobius_instance)
|
30
|
+ end
|
31
|
+
|
32
|
+ @impl GenServer
|
33
|
+ def init(args) do
|
34
|
+ instance = args[:mobius_instance] || :mobius
|
35
|
+ {reporter, reporter_args} = get_reporter(args)
|
36
|
+ {:ok, state} = reporter.init(reporter_args)
|
37
|
+ report_interval = args[:report_interval] || 60_000
|
38
|
+
|
39
|
+ timer_ref = Process.send_after(self(), :report, report_interval)
|
40
|
+
|
41
|
+ {:ok,
|
42
|
+ %{
|
43
|
+ reporter: reporter,
|
44
|
+ reporter_state: state,
|
45
|
+ report_interval: report_interval,
|
46
|
+ interval_ref: timer_ref,
|
47
|
+ next_query_from: nil,
|
48
|
+ mobius: instance
|
49
|
+ }}
|
50
|
+ end
|
51
|
+
|
52
|
+ defp get_reporter(args) do
|
53
|
+ case Keyword.fetch!(args, :reporter) do
|
54
|
+ {reporter, _client_args} = return when is_atom(reporter) -> return
|
55
|
+ reporter when is_atom(reporter) -> {reporter, []}
|
56
|
+ end
|
57
|
+ end
|
58
|
+
|
59
|
+ @impl GenServer
|
60
|
+ def handle_info(:report, state) do
|
61
|
+ {from, to} = get_query_window(state)
|
62
|
+ records = Scraper.all(state.mobius, from: from, to: to)
|
63
|
+ new_timer_ref = Process.send_after(self(), :report, state.report_interval)
|
64
|
+
|
65
|
+ case state.reporter.handle_metrics(records, state.reporter_state) do
|
66
|
+ {:noreply, new_state} ->
|
67
|
+ {:noreply,
|
68
|
+ %{
|
69
|
+ state
|
70
|
+ | reporter_state: new_state,
|
71
|
+ next_query_from: to + 1,
|
72
|
+ interval_ref: new_timer_ref
|
73
|
+ }}
|
74
|
+ end
|
75
|
+ end
|
76
|
+
|
77
|
+ def get_query_window(%{next_query_from: nil} = state) do
|
78
|
+ now = now()
|
79
|
+ subtract = div(state.report_interval, 1000)
|
80
|
+
|
81
|
+ {now - subtract, now}
|
82
|
+ end
|
83
|
+
|
84
|
+ def get_query_window(state) do
|
85
|
+ {state.next_query_from, now()}
|
86
|
+ end
|
87
|
+
|
88
|
+ defp now() do
|
89
|
+ DateTime.to_unix(DateTime.utc_now(), :second)
|
90
|
+ end
|
91
|
+ end
|
added
lib/mobius/remote_reporters/logger_reporter.ex
|
@@ -0,0 +1,49 @@
|
1
|
+ defmodule Mobius.RemoteReporters.LoggerReporter do
|
2
|
+ @moduledoc """
|
3
|
+ Example remote reporter that logs the first and last metric
|
4
|
+
|
5
|
+ This logger is used in the example application.
|
6
|
+ """
|
7
|
+
|
8
|
+ @behaviour Mobius.RemoteReporter
|
9
|
+
|
10
|
+ require Logger
|
11
|
+
|
12
|
+ @impl Mobius.RemoteReporter
|
13
|
+ def init(_) do
|
14
|
+ {:ok, nil}
|
15
|
+ end
|
16
|
+
|
17
|
+ @impl Mobius.RemoteReporter
|
18
|
+ def handle_metrics(metrics, state) do
|
19
|
+ groups =
|
20
|
+ Enum.group_by(metrics, fn %{name: name, tags: tags, type: type} -> {name, type, tags} end)
|
21
|
+
|
22
|
+ out =
|
23
|
+ Enum.reduce(groups, "", fn {{name, type, tags}, grouped_metrics}, str ->
|
24
|
+ first = List.first(grouped_metrics)
|
25
|
+ last = List.last(grouped_metrics)
|
26
|
+
|
27
|
+ str <>
|
28
|
+ """
|
29
|
+ #{name}, #{inspect(type)}, #{inspect(tags)}
|
30
|
+
|
31
|
+ First: #{inspect(first.timestamp)}: #{inspect(first.value)}
|
32
|
+ Last: #{inspect(last.timestamp)}: #{inspect(last.value)}
|
33
|
+
|
34
|
+ """
|
35
|
+ end)
|
36
|
+
|
37
|
+ Logger.info("""
|
38
|
+
|
39
|
+ ======
|
40
|
+
|
41
|
+ Mobius LoggerReporter:
|
42
|
+
|
43
|
+ #{out}
|
44
|
+ ======
|
45
|
+ """)
|
46
|
+
|
47
|
+ {:noreply, state}
|
48
|
+ end
|
49
|
+ end
|
changed
lib/mobius/summary.ex
|
@@ -4,7 +4,7 @@ defmodule Mobius.Summary do
|
4
4
|
@typedoc """
|
5
5
|
Calculated summary statistics
|
6
6
|
"""
|
7
|
- @type t() :: %{min: integer(), max: integer(), average: integer()}
|
7
|
+ @type t() :: %{min: integer(), max: integer(), average: float(), std_dev: float()}
|
8
8
|
|
9
9
|
@typedoc """
|
10
10
|
A data type to store snapshot information about a summary in order
|
|
@@ -14,6 +14,7 @@ defmodule Mobius.Summary do
|
14
14
|
min: integer(),
|
15
15
|
max: integer(),
|
16
16
|
accumulated: integer(),
|
17
|
+ accumulated_sqrd: integer(),
|
17
18
|
reports: non_neg_integer()
|
18
19
|
}
|
19
20
|
|
|
@@ -22,18 +23,25 @@ defmodule Mobius.Summary do
|
22
23
|
"""
|
23
24
|
@spec new(integer()) :: data()
|
24
25
|
def new(metric_value) do
|
25
|
- %{min: metric_value, max: metric_value, accumulated: metric_value, reports: 1}
|
26
|
+ %{
|
27
|
+ min: metric_value,
|
28
|
+ max: metric_value,
|
29
|
+ accumulated: metric_value,
|
30
|
+ accumulated_sqrd: metric_value * metric_value,
|
31
|
+ reports: 1
|
32
|
+ }
|
26
33
|
end
|
27
34
|
|
28
35
|
@doc """
|
29
36
|
Update a summary `data()` with new information based of a metric value
|
30
37
|
"""
|
31
|
- @spec update(data(), non_neg_integer()) :: data()
|
38
|
+ @spec update(data(), integer()) :: data()
|
32
39
|
def update(summary_data, new_metric_value) do
|
33
40
|
%{
|
34
41
|
min: min(summary_data.min, new_metric_value),
|
35
42
|
max: max(summary_data.max, new_metric_value),
|
36
43
|
accumulated: summary_data.accumulated + new_metric_value,
|
44
|
+ accumulated_sqrd: summary_data.accumulated_sqrd + new_metric_value * new_metric_value,
|
37
45
|
reports: summary_data.reports + 1
|
38
46
|
}
|
39
47
|
end
|
|
@@ -46,7 +54,17 @@ defmodule Mobius.Summary do
|
46
54
|
%{
|
47
55
|
min: summary_data.min,
|
48
56
|
max: summary_data.max,
|
49
|
- average: round(summary_data.accumulated / summary_data.reports)
|
57
|
+ average: summary_data.accumulated / summary_data.reports,
|
58
|
+ std_dev:
|
59
|
+ std_dev(summary_data.accumulated, summary_data.accumulated_sqrd, summary_data.reports)
|
50
60
|
}
|
51
61
|
end
|
62
|
+
|
63
|
+ defp std_dev(_sum, _sum_sqrd, 1), do: 0
|
64
|
+
|
65
|
+ # Naive algorithm. See Wikipedia
|
66
|
+ defp std_dev(sum, sum_sqrd, n) do
|
67
|
+ ((sum_sqrd - sum * sum / n) / (n - 1))
|
68
|
+ |> :math.sqrt()
|
69
|
+ end
|
52
70
|
end
|
changed
mix.exs
|
@@ -1,7 +1,7 @@
|
1
1
|
defmodule Mobius.MixProject do
|
2
2
|
use Mix.Project
|
3
3
|
|
4
|
- @version "0.4.0"
|
4
|
+ @version "0.5.0"
|
5
5
|
|
6
6
|
def project do
|
7
7
|
[
|
|
@@ -42,7 +42,7 @@ defmodule Mobius.MixProject do
|
42
42
|
extras: ["README.md", "CHANGELOG.md"],
|
43
43
|
main: "readme",
|
44
44
|
source_ref: "v#{@version}",
|
45
|
- source_url: "https://github.com/mattludwigs/mobius",
|
45
|
+ source_url: "https://github.com/mobius-home/mobius",
|
46
46
|
skip_undefined_reference_warnings_on: ["CHANGELOG.md"],
|
47
47
|
assets: "assets",
|
48
48
|
logo: "assets/m.png"
|
|
@@ -62,7 +62,7 @@ defmodule Mobius.MixProject do
|
62
62
|
|
63
63
|
defp dialyzer() do
|
64
64
|
[
|
65
|
- flags: [:unmatched_returns, :error_handling, :race_conditions],
|
65
|
+ flags: [:unmatched_returns, :error_handling],
|
66
66
|
plt_add_apps: [:eex, :mix]
|
67
67
|
]
|
68
68
|
end
|