update
This commit is contained in:
4
phoenix/deps/telemetry_metrics/.formatter.exs
Normal file
4
phoenix/deps/telemetry_metrics/.formatter.exs
Normal file
@@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
BIN
phoenix/deps/telemetry_metrics/.hex
Normal file
BIN
phoenix/deps/telemetry_metrics/.hex
Normal file
Binary file not shown.
133
phoenix/deps/telemetry_metrics/CHANGELOG.md
Normal file
133
phoenix/deps/telemetry_metrics/CHANGELOG.md
Normal 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`
|
||||
177
phoenix/deps/telemetry_metrics/LICENSE
Normal file
177
phoenix/deps/telemetry_metrics/LICENSE
Normal 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
|
||||
29
phoenix/deps/telemetry_metrics/README.md
Normal file
29
phoenix/deps/telemetry_metrics/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Telemetry.Metrics
|
||||
|
||||
[](https://github.com/beam-telemetry/telemetry_metrics/actions/workflows/ci.yml)
|
||||
[](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.
|
||||
25
phoenix/deps/telemetry_metrics/hex_metadata.config
Normal file
25
phoenix/deps/telemetry_metrics/hex_metadata.config
Normal 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">>]}.
|
||||
806
phoenix/deps/telemetry_metrics/lib/telemetry_metrics.ex
Normal file
806
phoenix/deps/telemetry_metrics/lib/telemetry_metrics.ex
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
31
phoenix/deps/telemetry_metrics/lib/telemetry_metrics/sum.ex
Normal file
31
phoenix/deps/telemetry_metrics/lib/telemetry_metrics/sum.ex
Normal 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
|
||||
@@ -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
|
||||
82
phoenix/deps/telemetry_metrics/mix.exs
Normal file
82
phoenix/deps/telemetry_metrics/mix.exs
Normal 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
|
||||
Reference in New Issue
Block a user