This commit is contained in:
2026-07-14 22:50:46 +04:00
parent 27143319e3
commit bd19e0682b
3116 changed files with 467189 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

Binary file not shown.

View File

@@ -0,0 +1,133 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v1.1.0)
### Changed
Add support for measurements as second argument of keep/drop callback.
## [1.0.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v1.0.0)
No changes. API is considered stable.
## [0.6.2](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.6.2)
### Changed
Fix warnings and make some internal functions private.
## [0.6.1](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.6.1)
### Added
* Support latest telemetry
## [0.6.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.6.0)
This release brings an improvement that helps to track metrics based on event's metadata.
The `:measurement` option now accepts a two-arity function that takes both event measurements
and metadata, and returns a final measurement to be tracked by the metric.
Starting with this version Telemetry.Metrics requires minimum Elixir 1.7.0.
### Changed
* Extended the `:measurement` option to accept a function taking both event measurements
and metadata.
## [0.5.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.5.0)
This release brings two new metric options, `:keep` and `:drop`, which allow users to specify
which telemetry events should be taken into account when collecting metrics. Reporters
supporting this version of `Telemetry.Metrics` need to respect these filtering rules.
In addition, the requirement to specify the distribution buckets has been dropped, since not
all reporters require configuration of buckets up front. Reporters which need the buckets to
be defined on the metric struct should accept it via `:reporter_options`.
### Added
* `:keep` and `:drop` options to filter events based on metadata
### Removed
* the `:buckets` option on the distribution metric
## [0.4.2](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.4.2)
### Fixed
* fixed a bug where unit conversion would fail if the measurement was missing
## [0.4.1](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.4.1)
### Fixed
* fixed a bug where unit conversion would fail if the measurement was `nil`
## [0.4.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.4.0)
### Added
* add `:reporter_options` option to all metric definitions for supplying reporter-specific
configuration
### Changed
* `Telemetry.Metrics.t` type is now defined as a union of base metric definitions provided by
the library
## [0.3.1](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.3.1)
### Added
* Add support for bytes, kilobytes and megabytes conversion
## [0.3.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.3.0)
### Added
* add `Telemetry.Metrics.ConsoleReporter` as an example reporter that prints data to the terminal
* add `summary/2` metric type
* add shortcut representation for `:buckets` in the `distribution/2` metric
## [0.2.1](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.2.1)
### Fixed
* dialyzer no longer fails on valid calls to `distribution/2` with `:buckets` option
## [0.2.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.2.0)
This release makes the library compatible with Telemetry v0.4.0. This means that metric values are
now based on one of the measurements. The first argument to all metric definition now specifies
the metric name, but also the source event name and the measurement - however, both of them can be
overridden using options.
### Added
* support for Telemetry v0.4.0 - the measurement can be configured indirectly via metric name or a
`:measurement` option
* `:tag_values` option to apply final transformations to event metadata before it's used for tags
* `:event_name` and `:measurement` to override event name and measurement set via metric name
* ability to convert time unit of measurement via `:unit` option
### Changed
* first argument to all metric definitions is a metric name instead of event name
* `:unit` option now also accepts a tuple specifying the conversion of time unit of measurement
### Removed
* `:metadata` option - `:tag_values` can be used to transform event metadata now instead
## [0.1.0](https://github.com/beam-telemetry/telemetry_metrics/tree/v0.1.0)
### Added
* four metric specifications, `counter/2`, `sum/2`, `last_value/2` and `distribution/2`

View File

@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,29 @@
# Telemetry.Metrics
[![CI](https://github.com/beam-telemetry/telemetry_metrics/actions/workflows/ci.yml/badge.svg)](https://github.com/beam-telemetry/telemetry_metrics/actions/workflows/ci.yml)
[![Codecov](https://codecov.io/gh/beam-telemetry/telemetry_metrics/branch/master/graphs/badge.svg)](https://codecov.io/gh/beam-telemetry/telemetry_metrics/branch/master/graphs/badge.svg)
Telemetry.Metrics provides a common interface for defining metrics based on
[`:telemetry`](https://github.com/beam-telemetry/telemetry) events. These metrics
can then be published to different backends using our Reporters API. See the
[official documentation](https://hexdocs.pm/telemetry_metrics) for more information.
## Reporters
The following reporters are available:
* [peep](https://github.com/rkallos/peep) - reporter for Prometheus and StatsD
* [telemetry_metrics_statsd](https://github.com/beam-telemetry/telemetry_metrics_statsd) - reporter for StatsD
* [telemetry_metrics_prometheus](https://github.com/beam-telemetry/telemetry_metrics_prometheus) - reporter for Prometheus
Other libraries are also available to report telemetry events, albeit without using Telemetry.Metrics, such as [OpenTelemetry bindings for telemetry](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/opentelemetry_telemetry).
## Copyright and License
Telemetry.Metrics is copyright (c) 2019 Erlang Ecosystem Foundation and Erlang Solutions.
Telemetry.Metrics source code is released under Apache License, Version 2.0.
See [LICENSE](LICENSE) and [NOTICE](NOTICE) files for more information.

View File

@@ -0,0 +1,25 @@
{<<"links">>,
[{<<"GitHub">>,<<"https://github.com/beam-telemetry/telemetry_metrics">>}]}.
{<<"name">>,<<"telemetry_metrics">>}.
{<<"version">>,<<"1.1.0">>}.
{<<"description">>,
<<"Provides a common interface for defining metrics based on Telemetry events.">>}.
{<<"elixir">>,<<"~> 1.10">>}.
{<<"files">>,
[<<"lib">>,<<"lib/telemetry_metrics">>,
<<"lib/telemetry_metrics/distribution.ex">>,
<<"lib/telemetry_metrics/last_value.ex">>,
<<"lib/telemetry_metrics/sum.ex">>,<<"lib/telemetry_metrics/counter.ex">>,
<<"lib/telemetry_metrics/console_reporter.ex">>,
<<"lib/telemetry_metrics/summary.ex">>,<<"lib/telemetry_metrics.ex">>,
<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,
<<"CHANGELOG.md">>]}.
{<<"app">>,<<"telemetry_metrics">>}.
{<<"licenses">>,[<<"Apache-2.0">>]}.
{<<"requirements">>,
[[{<<"name">>,<<"telemetry">>},
{<<"app">>,<<"telemetry">>},
{<<"optional">>,false},
{<<"requirement">>,<<"~> 0.4 or ~> 1.0">>},
{<<"repository">>,<<"hexpm">>}]]}.
{<<"build_tools">>,[<<"mix">>]}.

View File

@@ -0,0 +1,806 @@
defmodule Telemetry.Metrics do
@moduledoc """
Common interface for defining metrics based on
[`:telemetry`](https://github.com/beam-telemetry/telemetry) events.
Metrics are aggregations of Telemetry events with specific names, providing
a view of the system's behaviour over time.
To give a more concrete example, imagine that somewhere in your code there is
a function which sends an HTTP request, measures the time it took to get a
response, and emits an event with the information:
:telemetry.execute([:http, :request, :stop], %{duration: duration})
You could define a counter metric, which counts how many HTTP requests were
completed:
Telemetry.Metrics.counter("http.request.stop.duration")
or you could use a summary metric to see statistics about the request duration:
Telemetry.Metrics.summary("http.request.stop.duration")
This documentation is going to cover all the available metrics and how to use
them, as well as options, and how to integrate those metrics with reporters.
## Metrics
There are five metric types provided by `Telemetry.Metrics`:
* `counter/2` which counts the total number of emitted events
* `sum/2` which keeps track of the sum of selected measurement
* `last_value/2` holding the value of the selected measurement from
the most recent event
* `summary/2` calculating statistics of the selected measurement,
like maximum, mean, percentiles etc.
* `distribution/2` which builds a histogram of selected measurement
The first argument to all metric functions is the metric name. Metric
name can be provided as a string (e.g. `"http.request.stop.duration"`) or a
list of atoms (`[:http, :request, :stop, :duration]`). The metric name is
automatically used to infer the telemetry event and measurement. For example,
In the `"http.request.stop.duration"` example, the source event name is
`[:http, :request, :stop]` and metric values are drawn from `:duration`
measurement. Like this:
[:http , :request, :stop] :duration
<----- event name ------> <-- measurement -->
You can also explicitly specify the event name and measurement
if you prefer.
The second argument is a list of options. Below is the description of the
options common to all metric types:
* `:event_name` - the source event name. Can be represented either as a
string (e.g. `"http.request"`) or a list of atoms (`[:http, :request]`).
By default the event name is all but the last segment of the metric name.
* `:measurement` - the event measurement used as a source of a metric values.
By default it is the last segment of the metric name. It can be:
* a key in the event's measurements map
* an unary function accepting the whole measurements map and returning the
actual value to be used.
* a binary function accepting the measurements and metadata maps and
returning the actual value to be used.
* `:tags` - a subset of metadata keys by which aggregations will be broken down.
Defaults to an empty list.
* `:tag_values` - a function that receives the metadata and returns a map with
the tags as keys and their respective values. Defaults to returning the
metadata itself.
* `:keep` - a predicate function that evaluates the metadata and measurement
to conditionally record a given event. `:keep` and `:drop` cannot be
combined. Defaults to `nil`.
* `:drop` - a predicate function that evaluates the metadata and measurement
to conditionally skip recording a given event. `:keep` and `:drop` cannot
be combined. Defaults to `nil`.
* `:description` - human-readable description of the metric. Might be used by
reporters for documentation purposes. Defaults to `nil`.
* `:unit` - an atom describing the unit of selected measurement, typically in
singular, such as `:millisecond`, `:byte`, `:kilobyte`, etc. It may also be
a tuple indicating that a measurement should be converted from one unit to
another before a metric is updated. Currently, only time and byte unit
conversions are supported. We discuss those in detail in the "Converting Units"
section.
* `:reporter_options` - a keyword list of reporter-specific options for the metric.
## Breaking down metric values by tags
Sometimes it's not enough to have a global overview of all HTTP requests received
or all DB queries made. It's often more helpful to break down this data, for example,
we might want to have separate metric values for each unique database table and
operation name (`select`, `insert` etc.) to see how these particular queries behave.
This is where tagging comes into play. All metric definitions accept a `:tags` option:
counter("db.query.duration", tags: [:table, :operation])
The above definition means that we want to keep track of the number of queries, but
we want a separate counter for each unique pair of table and operation. Tag values are
fetched from event metadata - this means that in this example, `[:db, :query]` events
need to include `:table` and `:operation` keys in their payload:
:telemetry.execute([:db, :query], %{duration: 198}, %{table: "users", operation: "insert"})
:telemetry.execute([:db, :query], %{duration: 112}, %{table: "users", operation: "select"})
:telemetry.execute([:db, :query], %{duration: 201}, %{table: "sessions", operation: "insert"})
:telemetry.execute([:db, :query], %{duration: 212}, %{table: "sessions", operation: "insert"})
The result of aggregating the events above looks like this:
| table | operation | count |
| -------- | --------- | ----- |
| users | insert | 1 |
| users | select | 1 |
| sessions | insert | 2 |
The approach where we create a separate metric for some unique set of properties
is called a multi-dimensional data model.
### Transforming event metadata for tagging
Finally, sometimes there is a need to modify event metadata before it's used for
tagging. Each metric definition accepts a function in `:tag_values` option which
transforms the metadata into desired shape. Note that this function is called for
each event, so it's important to keep it fast if the rate of events is high.
## Converting Units
It might happen that the unit of measurement we're tracking is not the desirable unit
for the metric values, e.g. events are emitted by a 3rd-party library we do not control,
or a reporter we're using requires specific unit of measurement.
For these scenarios, each metric definition accepts a `:unit` option in a form of a tuple:
summary("http.request.stop.duration", unit: {from_unit, to_unit})
This means that the measurement will be converted from `from_unit` to `to_unit` before
being used for updating the metric. Currently, only time and byte conversions are
supported.
### Time Conversions
Most time measurements in the Erlang VM are done in the `:native` unit, which we need
to convert to the desired precision. The supported time units are: `:second`, `:millisecond`,
`:microsecond`, `:nanosecond` and `:native`.
For example, to convert HTTP request duration from `:native` time unit to milliseconds
you'd write:
summary("http.request.stop.duration", unit: {:native, :millisecond})
### Byte Conversions
Some metrics, like VM memory's usage are reported in bytes. You might want to convert this
to megabytes, for example. The supported byte units are: `:byte`, `:kilobyte` and `:megabyte`.
In order to convert a metric value from bytes to megabytes, you can write the following:
last_value("vm.memory.total", unit: {:byte, :megabyte})
## VM metrics
`Telemetry.Metrics` doesn't have a special treatment for the VM metrics - they need
to be based on the events like all other metrics.
`:telemetry_poller` package (http://hexdocs.pm/telemetry_poller) exposes a bunch of
VM-related metrics and also provides custom periodic measurements. You can add
telemetry poller as a dependency:
{:telemetry_poller, "~> 0.4"}
By simply adding `:telemetry_poller` as a dependency, two events will become available:
* `[:vm, :memory]` - contains the total memory, as well as the memory used for
binaries, processes, etc. See `erlang:memory/0` for all keys;
* `[:vm, :total_run_queue_lengths]` - returns the run queue lengths for CPU and
IO schedulers. It contains the `total`, `cpu` and `io` measurements;
You can consume those events with `Telemetry.Metrics` with the following sample metrics:
last_value("vm.memory.total", unit: :byte)
last_value("vm.total_run_queue_lengths.total")
last_value("vm.total_run_queue_lengths.cpu")
last_value("vm.total_run_queue_lengths.io")
If you want to change the frequency of those measurements, you can set the
following configuration in your config file:
config :telemetry_poller, :default, period: 5_000 # the default
Or disable it completely with:
config :telemetry_poller, :default, false
The `:telemetry_poller` package also allows you to run your own poller, which is
useful to retrieve process information or perform custom measurements periodically.
For example, to keep track of the number of users, inside a supervision tree, you could do:
measurements = [
{:process_info,
event: [:my_app, :worker],
name: MyApp.Worker,
keys: [:message_queue_len, :memory]},
{MyApp, :measure_users, []}
]
Supervisor.start_link([
# Run the given measurements every 10 seconds
{:telemetry_poller, measurements: measurements(), period: 10_000}
], strategy: :one_for_one)
Where `MyApp.measure_users/0` could be written like this:
defmodule MyApp do
def measure_users do
:telemetry.execute([:my_app, :users], %{total: MyApp.users_count()}, %{})
end
end
Now with measurements in place, you can define the metrics for the
events above:
last_value("my_app.worker.memory", unit: :byte)
last_value("my_app.worker.message_queue_len")
last_value("my_app.users.total")
## Optionally Recording Metric Events
There may be occasions where you want to record metrics differently or not at all based
upon metadata. Rather than depending on changing event names with prefixes, you can instead
provide a predicate function which returns true when you want the metric to be
processed for this event and false when you do not.
Let's examine some a few examples where optional recording can be helpful.
### Filtering on Metadata
Let's assume you are using an HTTP client library in your application which has the following
event_name: `[:http_client, :request, :stop]`. You use this library in multiple places and
you'd like to monitor the request duration.
You can use the event provided by the library but you have very different acceptable
performance requirements for a critical request, so it would be better to provide a different
metric name for monitoring. The client library includes a user configured `:name` option which
you can set and is passed in the event metadata.
Let's create a default distribution metric and one for the high performance call.
distribution(
"http.client.request.duration",
event_name: [:http_client, :request, :stop],
drop: &(match?(%{name: :fast_client}, &1))
)
distribution(
"http.fast.client.request.duration",
event_name: [:http_client, :request, :stop],
keep: &(match?(%{name: :fast_client}, &1))
)
With this configuration, you can now monitor these requests separately. In the first example,
any events where the client's name is `:fast_client` will be dropped for that metric.
Conversely, any matching events in the second example metric will be kept.
Note: only `:keep` OR `:drop` may be set, never both.
### Other Uses
Another potential use case for `:keep | :drop` could be per metric sampling rates. As long
as your function returns a boolean, it can determine if the event should be processed.
### Reporter Support
Event optional recording must be supported by the reporter you are using. Check your reporter's
documentation before relying on this functionality.
The `keep` function should be evaluated by reporters _prior_ to `tag_values` using the
raw `:telemetry.metadata()` values from the event.
## Reporters
So far, we have talked about metrics and how to describe them, but we haven't discussed
how those metrics are consumed and published to a system that provides data visualization,
aggregation, and more. The job of subscribing to events and processing the actual metrics
is a responsibility of reporters.
Generally speaking, a reporter is a process that you would start in your supervision
tree with a list of metrics as input. For example, `Telemetry.Metrics` ships with a
`Telemetry.Metrics.ConsoleReporter` module, which prints data to the terminal as an
example. You would start it as follows:
metrics = [
last_value("my_app.worker.memory", unit: :byte),
last_value("my_app.worker.message_queue_len"),
last_value("my_app.users.total")
]
Supervisor.start_link([
{Telemetry.Metrics.ConsoleReporter, metrics: metrics}
], strategy: :one_for_one)
Reporters take metric definitions as an input, subscribe to relevant events and
aggregate data when the events are emitted. Reporters may push metrics to StatsD,
some time-series database, or exposing a HTTP endpoint for Prometheus to scrape.
In a nutshell, `Telemetry.Metrics` defines only how metrics of particular type
should behave and reporters provide the actual implementation for these aggregations.
Official reporters, maintained by the Observability Working Group of the Erlang
Ecosystem Foundation, can be found on the [BEAM Telemetry organization on GitHub](https://github.com/beam-telemetry/).
You may also [find community reporters on hex.pm](https://hex.pm/packages?search=telemetry_metrics).
You can also read the [Writing Reporters](writing_reporters.html) page for general
information on how to write a reporter.
## Wiring it all up
Over the previous sections we discussed how to setup metrics and pass them to reporters
and how to configure a poller for measurements. We can wire it all up into a single
module as shown below. The example below would be used in the context of a Phoenix
application, where we have web metrics, database metrics (through Ecto) as well as
from the database, Phoenix metrics as well as VM metrics.
The first step is to add both `:telemetry_metrics` and `:telemetry_poller` as
dependencies:
[
{:telemetry_poller, "~> 0.4"},
{:telemetry_metrics, "~> 0.4"}
]
Then you could define a module that wires everything up:
defmodule MyAppWeb.Telemetry do
use Supervisor
import Telemetry.Metrics
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
end
def init(_arg) do
children = [
{:telemetry_poller,
measurements: periodic_measurements(),
period: 10_000},
# Or TelemetryMetricsPrometheus or TelemetryMetricsFooBar
{TelemetryMetricsStatsd, metrics: metrics()}
]
Supervisor.init(children, strategy: :one_for_one)
end
defp metrics do
[
# VM Metrics
last_value("vm.memory.total", unit: :byte),
last_value("vm.total_run_queue_lengths.total"),
last_value("vm.total_run_queue_lengths.cpu"),
last_value("vm.total_run_queue_lengths.io"),
last_value("my_app.worker.memory", unit: :byte),
last_value("my_app.worker.message_queue_len"),
# Database Time Metrics
summary("my_app.repo.query.total_time", unit: {:native, :millisecond}),
summary("my_app.repo.query.decode_time", unit: {:native, :millisecond}),
summary("my_app.repo.query.query_time", unit: {:native, :millisecond}),
summary("my_app.repo.query.idle_time", unit: {:native, :millisecond}),
summary("my_app.repo.query.queue_time", unit: {:native, :millisecond}),
# Phoenix Time Metrics
summary("phoenix.endpoint.stop.duration",
unit: {:native, :millisecond}),
summary(
"phoenix.router_dispatch.stop.duration",
unit: {:native, :millisecond},
tags: [:plug]
)
]
end
defp periodic_measurements do
[
{:process_info,
event: [:my_app, :worker],
name: Rumbl.Worker,
keys: [:message_queue_len, :memory]}
]
end
end
"""
require Logger
alias Telemetry.Metrics.{Counter, Sum, LastValue, Summary, Distribution}
@typedoc """
The name of the metric, either as string or a list of atoms.
"""
@type metric_name :: String.t() | normalized_metric_name()
@typedoc """
The name of the metric represented as a list of atoms.
"""
@type normalized_metric_name :: [atom(), ...]
@type measurement ::
term()
| (:telemetry.event_measurements() -> number())
| (:telemetry.event_measurements(), :telemetry.event_metadata() -> number())
@type tag :: term()
@type tags :: [tag()]
@type tag_values :: (:telemetry.event_metadata() -> :telemetry.event_metadata())
@type predicate_fun ::
(:telemetry.event_metadata() -> boolean())
| (:telemetry.event_metadata(), :telemetry.event_measurements() -> boolean())
@type keep :: predicate_fun()
@type drop :: predicate_fun()
@type description :: nil | String.t()
@type unit :: atom()
@type time_unit_conversion() :: {time_unit(), time_unit()}
@type time_unit() :: :second | :millisecond | :microsecond | :nanosecond | :native
@type byte_unit() :: :megabyte | :kilobyte | :byte
@type byte_unit_conversion() :: {byte_unit(), byte_unit()}
@type reporter_options :: keyword()
@type metric_option ::
{:event_name, String.t() | :telemetry.event_name()}
| {:measurement, measurement()}
| {:tags, tags()}
| {:tag_values, tag_values()}
| {:keep, keep()}
| {:drop, drop()}
| {:description, description()}
| {:unit, unit() | time_unit_conversion() | byte_unit_conversion()}
| {:reporter_options, reporter_options()}
@type metric_options :: [metric_option()]
@typedoc """
One of the base metric definitions.
"""
@type t() :: Counter.t() | LastValue.t() | Sum.t() | Summary.t() | Distribution.t()
# API
@doc """
Returns a definition of counter metric.
Counter metric keeps track of the total number of specific events emitted.
The value of the counter is always incremented by one, regardless of the
value of the measurement. However, note the measurement must still be
available in the event, otherwise the event is not accounted for.
See the ["Metrics"](#module-metrics) section in the top-level documentation of this module for more
information.
## Example
counter(
"http.request.count",
tags: [:controller, :action]
)
"""
@spec counter(metric_name(), metric_options()) :: Counter.t()
def counter(metric_name, options \\ []) do
struct(Counter, common_fields(metric_name, options))
end
@doc """
Returns a definition of sum metric.
Sum metric keeps track of the sum of selected measurement's values carried by specific events.
See the ["Metrics"](#module-metrics) section in the top-level documentation of this module for more
information.
## Example
sum(
"user.session_count",
event_name: "user.session_count",
measurement: :delta,
tags: [:role]
)
"""
@spec sum(metric_name(), metric_options()) :: Sum.t()
def sum(metric_name, options \\ []) do
struct(Sum, common_fields(metric_name, options))
end
@doc """
Returns a definition of last value metric.
Last value keeps track of the selected measurement found in the most recent event.
See the ["Metrics"](#module-metrics) section in the top-level documentation of this module for more
information.
## Example
last_value(
"vm.memory.total",
description: "Total amount of memory allocated by the Erlang VM", unit: :byte
)
"""
@spec last_value(metric_name(), metric_options()) :: LastValue.t()
def last_value(metric_name, options \\ []) do
struct(LastValue, common_fields(metric_name, options))
end
@doc """
Returns a definition of summary metric.
This metric aggregates measurement's values into statistics, e.g. minimum and maximum, mean, or
percentiles. It is up to the reporter to decide which statistics exactly are exposed.
See the ["Metrics"](#module-metrics) section in the top-level documentation of this module for more
information.
## Example
summary(
"db.query.duration",
tags: [:table],
unit: {:native, :millisecond}
)
"""
@spec summary(metric_name(), metric_options()) :: Summary.t()
def summary(metric_name, options \\ []) do
struct(Summary, common_fields(metric_name, options))
end
@doc """
Returns a definition of distribution metric.
Distribution metric builds a histogram of selected measurement's values. It is up to the reporter
to decide how the boundaries of the distribution buckets are configured - via `:reporter_options`,
configuration of the aggregating system, or other means.
See the ["Metrics"](#module-metrics) section in the top-level documentation of this module for more
information.
## Example
distribution(
"http.request.duration",
tags: [:controller, :action],
)
"""
@spec distribution(metric_name(), metric_options()) :: Distribution.t()
def distribution(metric_name, options \\ []) do
fields = common_fields(metric_name, options)
struct(Distribution, fields)
end
# Helpers
@spec common_fields(metric_name(), [metric_option() | {atom(), term()}]) :: map()
defp common_fields(metric_name, options) do
metric_name = validate_metric_or_event_name!(metric_name)
{event_name, [measurement]} = Enum.split(metric_name, -1)
{event_name, options} = Keyword.pop(options, :event_name, event_name)
{measurement, options} = Keyword.pop(options, :measurement, measurement)
{keep, options} = Keyword.pop(options, :keep)
{drop, options} = Keyword.pop(options, :drop)
validate_recording_rule_fun_options!(keep, drop)
keep = validate_recording_rule_fun!(keep)
drop = validate_recording_rule_fun!(drop)
event_name = validate_metric_or_event_name!(event_name)
{unit, options} = Keyword.pop(options, :unit, :unit)
{unit, conversion_ratio} = validate_unit!(unit)
measurement = maybe_convert_measurement(measurement, conversion_ratio)
validate_metric_options!(options)
options
|> fill_in_default_metric_options()
|> Map.new()
|> Map.merge(%{
name: metric_name,
event_name: event_name,
measurement: measurement,
keep: keep_fun(keep, drop),
unit: unit
})
end
@spec validate_metric_or_event_name!(term()) :: [atom(), ...]
defp validate_metric_or_event_name!(metric_or_event_name)
when metric_or_event_name == [] or metric_or_event_name == "" do
raise ArgumentError, "metric or event name can't be empty"
end
defp validate_metric_or_event_name!(metric_or_event_name) when is_list(metric_or_event_name) do
if Enum.all?(metric_or_event_name, &is_atom/1) do
metric_or_event_name
else
raise ArgumentError,
"expected metric or event name to be a list of atoms or a string, " <>
"got #{inspect(metric_or_event_name)}"
end
end
defp validate_metric_or_event_name!(metric_or_event_name)
when is_binary(metric_or_event_name) do
segments = String.split(metric_or_event_name, ".")
if Enum.any?(segments, &(&1 == "")) do
raise ArgumentError,
"metric or event name #{metric_or_event_name} contains leading, " <>
"trailing or consecutive dots"
end
Enum.map(segments, &String.to_atom/1)
end
defp validate_metric_or_event_name!(term) do
raise ArgumentError,
"expected metric name to be a string or a list of atoms, got #{inspect(term)}"
end
defp validate_recording_rule_fun!(nil), do: nil
defp validate_recording_rule_fun!(fun) when is_function(fun, 1) or is_function(fun, 2), do: fun
defp validate_recording_rule_fun!(term) do
raise ArgumentError,
"expected recording rule to be a function accepting either metadata or metadata and measurements, got #{inspect(term)}"
end
defp validate_recording_rule_fun_options!(keep, drop) when is_nil(keep) or is_nil(drop), do: :ok
defp validate_recording_rule_fun_options!(_keep, _drop) do
raise ArgumentError,
"either the keep or drop option may be set, but not both"
end
defp keep_fun(nil, nil), do: nil
defp keep_fun(keep, nil), do: keep
defp keep_fun(nil, drop) when is_function(drop, 1), do: &(not drop.(&1))
defp keep_fun(nil, drop) when is_function(drop, 2), do: &(not drop.(&1, &2))
@spec fill_in_default_metric_options([metric_option()]) :: [metric_option()]
defp fill_in_default_metric_options(options) do
Keyword.merge(default_metric_options(), options)
end
@spec default_metric_options() :: [metric_option()]
defp default_metric_options() do
[
tags: [],
tag_values: & &1,
nil: nil,
description: nil,
reporter_options: []
]
end
@spec validate_metric_options!([metric_option()]) :: :ok | no_return()
defp validate_metric_options!(options) do
if tags = Keyword.get(options, :tags), do: validate_tags!(tags)
if tag_values = Keyword.get(options, :tag_values), do: validate_tag_values!(tag_values)
if description = Keyword.get(options, :description), do: validate_description!(description)
if reporter_options = Keyword.get(options, :reporter_options),
do: validate_reporter_options!(reporter_options)
end
@spec validate_tags!(term()) :: :ok | no_return()
defp validate_tags!(list) when is_list(list) do
:ok
end
defp validate_tags!(term) do
raise ArgumentError, "expected tag keys to be a list, got: #{inspect(term)}"
end
@spec validate_tag_values!(term()) :: :ok | no_return()
defp validate_tag_values!(fun) when is_function(fun, 1) do
:ok
end
defp validate_tag_values!(term) do
raise ArgumentError,
"expected tag_values fun to be a one-argument function, got: #{inspect(term)}"
end
@spec validate_description!(term()) :: :ok | no_return()
defp validate_description!(term) do
if is_binary(term) and String.valid?(term) do
:ok
else
raise ArgumentError, "expected description to be a string, got #{inspect(term)}"
end
end
@spec validate_reporter_options!(term()) :: :ok | no_return()
defp validate_reporter_options!(reporter_options) do
if Keyword.keyword?(reporter_options) do
:ok
else
raise ArgumentError,
"expected reporter options to be a keyword list, got #{inspect(reporter_options)}"
end
end
@spec maybe_convert_measurement(measurement(), conversion_ratio :: non_neg_integer()) ::
measurement()
defp maybe_convert_measurement(measurement, 1) do
# Don't wrap measurement if no conversion is required.
measurement
end
defp maybe_convert_measurement(measurement, conversion_ratio)
when is_function(measurement, 2) do
fn measurements, metadata ->
if measurement = measurement.(measurements, metadata) do
measurement * conversion_ratio
end
end
end
defp maybe_convert_measurement(measurement, conversion_ratio)
when is_function(measurement, 1) do
fn measurements ->
if measurement = measurement.(measurements) do
measurement * conversion_ratio
end
end
end
defp maybe_convert_measurement(measurement, conversion_ratio) do
fn measurements ->
case measurements do
%{^measurement => nil} -> nil
%{^measurement => value} -> value * conversion_ratio
%{} -> nil
end
end
end
@spec validate_unit!(term()) :: {unit(), conversion_ratio :: number()} | no_return()
defp validate_unit!({from_unit, to_unit} = t) do
cond do
time_unit?(from_unit) and time_unit?(to_unit) ->
{to_unit, time_unit_conversion_ratio(from_unit, to_unit)}
byte_unit?(from_unit) and byte_unit?(to_unit) ->
{to_unit, byte_unit_conversion_ratio(from_unit, to_unit)}
true ->
raise ArgumentError,
"expected both elements of the unit conversion tuple " <>
"to represent time or byte units, got #{inspect(t)}"
end
end
defp validate_unit!(unit) when is_atom(unit) do
{unit, 1}
end
defp validate_unit!(term) do
raise ArgumentError,
"expected unit to be an atom or a two-element tuple, got #{inspect(term)}"
end
# Maybe warn or raise if the result of conversion is 0?
@spec time_unit_conversion_ratio(time_unit(), time_unit()) :: number()
defp time_unit_conversion_ratio(unit, unit), do: 1
defp time_unit_conversion_ratio(from_unit, to_unit) do
case System.convert_time_unit(1, from_unit, to_unit) do
0 ->
1 / System.convert_time_unit(1, to_unit, from_unit)
ratio ->
ratio
end
end
@spec byte_unit_conversion_ratio(byte_unit(), byte_unit()) :: number()
defp byte_unit_conversion_ratio(unit, unit), do: 1
defp byte_unit_conversion_ratio(from_unit, to_unit) do
multiplier = byte_unit_factor(to_unit)
divisor = byte_unit_factor(from_unit)
1 * multiplier / divisor
end
@spec byte_unit_factor(byte_unit()) :: number()
defp byte_unit_factor(:megabyte), do: 1
defp byte_unit_factor(:kilobyte), do: 1_000
defp byte_unit_factor(:byte), do: 1_000_000
@spec time_unit?(term()) :: boolean()
defp time_unit?(:native), do: true
defp time_unit?(:second), do: true
defp time_unit?(:millisecond), do: true
defp time_unit?(:microsecond), do: true
defp time_unit?(:nanosecond), do: true
defp time_unit?(_), do: false
@spec byte_unit?(term()) :: boolean()
defp byte_unit?(:byte), do: true
defp byte_unit?(:kilobyte), do: true
defp byte_unit?(:megabyte), do: true
defp byte_unit?(_), do: false
end

View File

@@ -0,0 +1,177 @@
defmodule Telemetry.Metrics.ConsoleReporter do
@moduledoc """
A reporter that prints events and metrics to the terminal.
This is useful for debugging and discovering all available
measurements and metadata in an event.
For example, imagine the given metrics:
metrics = [
last_value("vm.memory.binary", unit: :byte),
counter("vm.memory.total")
]
A console reporter can be started as a child of your supervision tree as:
{Telemetry.Metrics.ConsoleReporter, metrics: metrics}
Now when the "vm.memory" telemetry event is dispatched, we will see
reports like this:
[Telemetry.Metrics.ConsoleReporter] Got new event!
Event name: vm.memory
All measurements: %{binary: 100, total: 200}
All metadata: %{}
Metric measurement: :binary (last_value)
With value: 100 byte
And tag values: %{}
Metric measurement: :total (counter)
With value: 200
And tag values: %{}
In other words, every time there is an event for any of the registered
metrics, it prints the event measurement and metadata, and then it prints
information about each metric to the user.
"""
use GenServer
require Logger
def start_link(opts) do
server_opts = Keyword.take(opts, [:name])
device = opts[:device] || :stdio
metrics =
opts[:metrics] ||
raise ArgumentError, "the :metrics option is required by #{inspect(__MODULE__)}"
GenServer.start_link(__MODULE__, {metrics, device}, server_opts)
end
@impl true
def init({metrics, device}) do
Process.flag(:trap_exit, true)
groups = Enum.group_by(metrics, & &1.event_name)
for {event, metrics} <- groups do
id = {__MODULE__, event, self()}
:telemetry.attach(id, event, &__MODULE__.handle_event/4, {metrics, device})
end
{:ok, Map.keys(groups)}
end
@impl true
def terminate(_, events) do
for event <- events do
:telemetry.detach({__MODULE__, event, self()})
end
:ok
end
@doc false
def handle_event(event_name, measurements, metadata, {metrics, device}) do
prelude = """
[#{inspect(__MODULE__)}] Got new event!
Event name: #{Enum.join(event_name, ".")}
All measurements: #{inspect(measurements)}
All metadata: #{inspect(metadata)}
"""
parts =
for %struct{} = metric <- metrics do
header = """
Metric measurement: #{measurement_name(metric)} (#{metric(struct)})
"""
[
header
| try do
measurement = extract_measurement(metric, measurements, metadata)
tags = extract_tags(metric, metadata)
cond do
is_nil(measurement) ->
"""
Measurement value missing (metric skipped)
"""
not keep?(metric, metadata, measurements) ->
"""
Event dropped
"""
metric.__struct__ == Telemetry.Metrics.Counter ->
"""
Tag values: #{inspect(tags)}
"""
true ->
"""
With value: #{inspect(measurement)}#{unit(metric.unit)}#{info(measurement)}
Tag values: #{inspect(tags)}
"""
end
rescue
e ->
Logger.error([
"Could not format metric #{inspect(metric)}\n",
Exception.format(:error, e, __STACKTRACE__)
])
"""
Errored when processing (metric skipped - handler may detach!)
"""
end
]
end
IO.puts(device, [prelude | parts])
end
defp measurement_name(%{measurement: measurement}) when is_atom(measurement),
do: inspect(measurement)
defp measurement_name(%{measurement: fun, name: name}) when is_function(fun) do
measurement = List.last(name)
"#{inspect(measurement)} [via #{inspect(fun)}]"
end
defp keep?(metric, metadata, measurements) do
case metric do
%{keep: nil} -> true
%{keep: keep} when is_function(keep, 1) -> keep.(metadata)
%{keep: keep} when is_function(keep, 2) -> keep.(metadata, measurements)
end
end
defp extract_measurement(metric, measurements, metadata) do
case metric.measurement do
fun when is_function(fun, 2) -> fun.(measurements, metadata)
fun when is_function(fun, 1) -> fun.(measurements)
key -> measurements[key]
end
end
defp info(int) when is_number(int), do: ""
defp info(_), do: " (WARNING! measurement should be a number)"
defp unit(:unit), do: ""
defp unit(unit), do: " #{unit}"
defp metric(Telemetry.Metrics.Counter), do: "counter"
defp metric(Telemetry.Metrics.Distribution), do: "distribution"
defp metric(Telemetry.Metrics.LastValue), do: "last_value"
defp metric(Telemetry.Metrics.Sum), do: "sum"
defp metric(Telemetry.Metrics.Summary), do: "summary"
defp extract_tags(metric, metadata) do
tag_values = metric.tag_values.(metadata)
Map.take(tag_values, metric.tags)
end
end

View File

@@ -0,0 +1,31 @@
defmodule Telemetry.Metrics.Counter do
@moduledoc """
Defines a specification of counter metric.
"""
alias Telemetry.Metrics
defstruct [
:name,
:event_name,
:measurement,
:tags,
:tag_values,
:keep,
:description,
:unit,
:reporter_options
]
@type t :: %__MODULE__{
name: Metrics.normalized_metric_name(),
event_name: :telemetry.event_name(),
measurement: Metrics.measurement(),
tags: Metrics.tags(),
tag_values: Metrics.tag_values(),
keep: Metrics.keep(),
description: Metrics.description(),
unit: Metrics.unit(),
reporter_options: Metrics.reporter_options()
}
end

View File

@@ -0,0 +1,31 @@
defmodule Telemetry.Metrics.Distribution do
@moduledoc """
Defines a specification of distribution metric.
"""
alias Telemetry.Metrics
defstruct [
:name,
:event_name,
:measurement,
:tags,
:tag_values,
:keep,
:description,
:unit,
:reporter_options
]
@type t :: %__MODULE__{
name: Metrics.normalized_metric_name(),
event_name: :telemetry.event_name(),
measurement: Metrics.measurement(),
tags: Metrics.tags(),
tag_values: Metrics.tag_values(),
keep: Metrics.keep(),
description: Metrics.description(),
unit: Metrics.unit(),
reporter_options: Metrics.reporter_options()
}
end

View File

@@ -0,0 +1,31 @@
defmodule Telemetry.Metrics.LastValue do
@moduledoc """
Defines a specification of last value metric.
"""
alias Telemetry.Metrics
defstruct [
:name,
:event_name,
:measurement,
:tags,
:tag_values,
:keep,
:description,
:unit,
:reporter_options
]
@type t :: %__MODULE__{
name: Metrics.normalized_metric_name(),
event_name: :telemetry.event_name(),
measurement: Metrics.measurement(),
tags: Metrics.tags(),
tag_values: Metrics.tag_values(),
keep: Metrics.keep(),
description: Metrics.description(),
unit: Metrics.unit(),
reporter_options: Metrics.reporter_options()
}
end

View File

@@ -0,0 +1,31 @@
defmodule Telemetry.Metrics.Sum do
@moduledoc """
Defines a specification of sum metric.
"""
alias Telemetry.Metrics
defstruct [
:name,
:event_name,
:measurement,
:tags,
:tag_values,
:keep,
:description,
:unit,
:reporter_options
]
@type t :: %__MODULE__{
name: Metrics.normalized_metric_name(),
event_name: :telemetry.event_name(),
measurement: Metrics.measurement(),
tags: Metrics.tags(),
tag_values: Metrics.tag_values(),
keep: Metrics.keep(),
description: Metrics.description(),
unit: Metrics.unit(),
reporter_options: Metrics.reporter_options()
}
end

View File

@@ -0,0 +1,31 @@
defmodule Telemetry.Metrics.Summary do
@moduledoc """
Defines a specification of summary metric.
"""
alias Telemetry.Metrics
defstruct [
:name,
:event_name,
:measurement,
:tags,
:tag_values,
:keep,
:description,
:unit,
:reporter_options
]
@type t :: %__MODULE__{
name: Metrics.normalized_metric_name(),
event_name: :telemetry.event_name(),
measurement: Metrics.measurement(),
tags: Metrics.tags(),
tag_values: Metrics.tag_values(),
keep: Metrics.keep(),
description: Metrics.description(),
unit: Metrics.unit(),
reporter_options: Metrics.reporter_options()
}
end

View File

@@ -0,0 +1,82 @@
defmodule Telemetry.Metrics.MixProject do
use Mix.Project
@version "1.1.0"
def project do
[
name: "Telemetry.Metrics",
app: :telemetry_metrics,
version: @version,
elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
preferred_cli_env: preferred_cli_env(),
deps: deps(),
docs: docs(),
description: description(),
package: package(),
test_coverage: [tool: ExCoveralls]
]
end
def application do
[
extra_applications: [:logger]
]
end
defp preferred_cli_env do
[
docs: :docs,
dialyzer: :test,
"coveralls.json": :test
]
end
defp deps do
[
{:telemetry, "~> 0.4 or ~> 1.0"},
{:ex_doc, "~> 0.20", only: :docs},
{:dialyxir, "~> 1.0", only: :test, runtime: false},
{:excoveralls, "~> 0.14.0", only: :test, runtime: false}
]
end
defp docs do
[
main: "Telemetry.Metrics",
canonical: "http://hexdocs.pm/telemetry_metrics",
source_url: "https://github.com/beam-telemetry/telemetry_metrics",
source_ref: "v#{@version}",
extras: [
"docs/rationale.md",
"docs/writing_reporters.md"
],
groups_for_modules: [
# Telemetry.Metrics,
"Metrics Structs": [
Telemetry.Metrics.Counter,
Telemetry.Metrics.Distribution,
Telemetry.Metrics.LastValue,
Telemetry.Metrics.Sum,
Telemetry.Metrics.Summary
]
]
]
end
def description do
"""
Provides a common interface for defining metrics based on Telemetry events.
"""
end
defp package do
[
maintainers: ["Arkadiusz Gil", "José Valim"],
licenses: ["Apache-2.0"],
links: %{"GitHub" => "https://github.com/beam-telemetry/telemetry_metrics"}
]
end
end