Add Nebulex instrumentation library (#83)
Add instrumentation for Nebulex, a distributed cache library. This library provides solid telemetry support for this initial implementation. Caching implementation is mostly based on in-memory storage (like ETS) and RPC calls for distribution (via OTP libraries, like :erpc). AFAICT, there is not much specifics for how to translate into Semantic Attributes: those caches are not quite a DB, except maybe for the one which implements the storage; the RPC can't be reliably captured either. Given the above constraints, this initial implementation instruments the library via custom attributes (namespaced as `nebulex.*`). It's not 100% clear the behaviour of OTel for actual distributed caches - from my tests, that may create some orphan spans. I think that's fine as first release. Nebulex follow the patterns of Ecto, so this instrumentation follows a similar pattern of OpentelemetryEcto. It does include a `setup_all/1` function for convenience, that leverages the :init events Nebulex emit on process start. Co-authored-by: Tristan Sloughter <t@crashfast.com>
This commit is contained in:
parent
1e2ae6707c
commit
dfed96874f
|
@ -53,6 +53,9 @@ opentelemetry_cowboy:
|
|||
opentelemetry_ecto:
|
||||
- instrumentation/opentelemetry_ecto/**/*
|
||||
|
||||
opentelemetry_nebulex:
|
||||
- instrumentation/opentelemetry_nebulex/**/*
|
||||
|
||||
opentelemetry_oban:
|
||||
- instrumentation/opentelemetry_oban/**/*
|
||||
|
||||
|
|
|
@ -49,7 +49,43 @@ jobs:
|
|||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: opentelemetry_ecto_test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ matrix.otp_version }}
|
||||
elixir-version: ${{ matrix.elixir_version }}
|
||||
rebar3-version: ${{ matrix.rebar3_version }}
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/deps
|
||||
~/_build
|
||||
key: ${{ runner.os }}-build-${{ matrix.otp_version }}-${{ matrix.elixir_version }}-v3-${{ hashFiles('**/mix.lock') }}
|
||||
- name: Fetch deps
|
||||
if: steps.deps-cache.outputs.cache-hit != 'true'
|
||||
run: mix deps.get
|
||||
- name: Compile project
|
||||
run: mix compile --warnings-as-errors
|
||||
- name: Check formatting
|
||||
run: mix format --check-formatted
|
||||
- name: Test
|
||||
run: mix test
|
||||
|
||||
opentelemetry-nebulex:
|
||||
needs: [test-matrix]
|
||||
if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_nebulex'))
|
||||
env:
|
||||
app: 'opentelemetry_nebulex'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: instrumentation/${{ env.app }}
|
||||
runs-on: ubuntu-18.04
|
||||
name: Opentelemetry Nebulex test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: erlef/setup-beam@v1
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
/instrumentation/opentelemetry_cowboy @bryannaegele @tsloughter
|
||||
/instrumentation/opentelemetry_ecto @bryannaegele @tsloughter
|
||||
/instrumentation/opentelemetry_nebulex @andrewhr
|
||||
/instrumentation/opentelemetry_oban @indrekj
|
||||
/instrumentation/opentelemetry_phoenix @bryannaegele @tsloughter
|
||||
/instrumentation/opentelemetry_redix @andrewhr
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
opentelemetry_redix-*.tar
|
|
@ -0,0 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Initial release
|
|
@ -0,0 +1,201 @@
|
|||
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
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,29 @@
|
|||
# OpentelemetryNebulex
|
||||
|
||||
OpentelemetryNebulex uses `telemetry` handlers to create `OpenTelemetry` spans
|
||||
from Nebulex command events.
|
||||
|
||||
## Installation
|
||||
|
||||
The package can be installed by adding `opentelemetry_nebulex` to your list of
|
||||
dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:opentelemetry_nebulex, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
| OpentelemetryNebulex Version | Otel Version | Notes |
|
||||
| :--------------------------- | :----------- | :---- |
|
||||
| | | |
|
||||
| v0.1.0 | v1.0.0 | |
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/opentelemetry_nebulex](https://hexdocs.pm/opentelemetry_nebulex).
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
import Config
|
||||
|
||||
# This configuration is loaded before any dependency and is restricted
|
||||
# to this project. If another project depends on this project, this
|
||||
# file won't be loaded nor affect the parent project. For this reason,
|
||||
# if you want to provide default values for your application for
|
||||
# third-party users, it should be done in your "mix.exs" file.
|
||||
|
||||
# You can configure your application as:
|
||||
#
|
||||
# config :opentelemetry_nebulex, key: :value
|
||||
#
|
||||
# and access this configuration in your application as:
|
||||
#
|
||||
# Application.get_env(:opentelemetry_nebulex, :key)
|
||||
#
|
||||
# You can also configure a third-party app:
|
||||
#
|
||||
# config :logger, level: :info
|
||||
#
|
||||
|
||||
# It is also possible to import configuration files, relative to this
|
||||
# directory. For example, you can emulate configuration per environment
|
||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
||||
# Configuration from the imported file will override the ones defined
|
||||
# here (which is why it is important to import them last).
|
||||
#
|
||||
try do
|
||||
import_config "#{Mix.env()}.exs"
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
import Config
|
||||
|
||||
config :opentelemetry,
|
||||
processors: [{:otel_simple_processor, %{}}]
|
|
@ -0,0 +1,111 @@
|
|||
defmodule OpentelemetryNebulex do
|
||||
@moduledoc """
|
||||
OpentelemetryNebulex uses `telemetry` handlers to create `OpenTelemetry` spans
|
||||
from Nebulex command events.
|
||||
"""
|
||||
|
||||
@tracer_id __MODULE__
|
||||
|
||||
@doc """
|
||||
Initializes and configures telemetry handlers for a given cache.
|
||||
|
||||
Example:
|
||||
|
||||
OpentelemetryNebulex.setup([:blog, :partitioned_cache])
|
||||
"""
|
||||
def setup(event_prefix, opts \\ []) do
|
||||
:telemetry.attach(
|
||||
{__MODULE__, event_prefix, :command_start},
|
||||
event_prefix ++ [:command, :start],
|
||||
&__MODULE__.handle_command_start/4,
|
||||
opts
|
||||
)
|
||||
|
||||
:telemetry.attach(
|
||||
{__MODULE__, event_prefix, :command_stop},
|
||||
event_prefix ++ [:command, :stop],
|
||||
&__MODULE__.handle_command_stop/4,
|
||||
opts
|
||||
)
|
||||
|
||||
:telemetry.attach(
|
||||
{__MODULE__, event_prefix, :command_exception},
|
||||
event_prefix ++ [:command, :exception],
|
||||
&__MODULE__.handle_command_exception/4,
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Initializes and configures telemetry handlers for all caches.
|
||||
|
||||
Use the `[:nebulex, :cache, :init]` event to automatically discover caches, and attach
|
||||
the handlers dynamically. It only works for caches that start after this function is called.
|
||||
|
||||
Example:
|
||||
|
||||
OpentelemetryNebulex.setup_all()
|
||||
"""
|
||||
def setup_all(opts \\ []) do
|
||||
:telemetry.attach(
|
||||
__MODULE__,
|
||||
[:nebulex, :cache, :init],
|
||||
&__MODULE__.handle_init/4,
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_init(_event, _measurements, metadata, config) do
|
||||
setup(metadata[:opts][:telemetry_prefix], config)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_command_start(_event, _measurements, metadata, _config) do
|
||||
span_name = "nebulex #{metadata.function_name}"
|
||||
|
||||
attributes =
|
||||
%{
|
||||
"nebulex.cache": metadata.adapter_meta.cache
|
||||
}
|
||||
|> maybe_put(:"nebulex.backend", metadata.adapter_meta[:backend])
|
||||
|> maybe_put(:"nebulex.keyslot", metadata.adapter_meta[:keyslot])
|
||||
|> maybe_put(:"nebulex.model", metadata.adapter_meta[:model])
|
||||
|
||||
OpentelemetryTelemetry.start_telemetry_span(@tracer_id, span_name, metadata, %{
|
||||
attributes: attributes
|
||||
})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_command_stop(_event, _measurements, metadata, _config) do
|
||||
ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
|
||||
|
||||
if action = extract_action(metadata) do
|
||||
OpenTelemetry.Span.set_attribute(ctx, :"nebulex.action", action)
|
||||
end
|
||||
|
||||
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_command_exception(_event, _measurements, metadata, _config) do
|
||||
ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
|
||||
|
||||
OpenTelemetry.Span.record_exception(ctx, metadata.reason, metadata.stacktrace)
|
||||
OpenTelemetry.Tracer.set_status(OpenTelemetry.status(:error, format_error(metadata.reason)))
|
||||
|
||||
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
|
||||
end
|
||||
|
||||
defp maybe_put(attributes, _key, nil), do: attributes
|
||||
defp maybe_put(attributes, key, value), do: Map.put(attributes, key, value)
|
||||
|
||||
defp extract_action(%{function_name: f, result: :"$expired"}) when f in [:get, :take], do: :miss
|
||||
defp extract_action(%{function_name: f, result: nil}) when f in [:get, :take], do: :miss
|
||||
defp extract_action(%{function_name: f, result: _}) when f in [:get, :take], do: :hit
|
||||
defp extract_action(_), do: nil
|
||||
|
||||
defp format_error(exception) when is_exception(exception), do: Exception.message(exception)
|
||||
defp format_error(error), do: inspect(error)
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
defmodule OpentelemetryNebulex.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :opentelemetry_nebulex,
|
||||
description: description(),
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.11",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
package: package(),
|
||||
source_url:
|
||||
"https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_nebulex"
|
||||
]
|
||||
end
|
||||
|
||||
defp description do
|
||||
"OpenTelemetry tracing for Nebulex"
|
||||
end
|
||||
|
||||
defp package do
|
||||
[
|
||||
files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG*),
|
||||
licenses: ["Apache-2.0"],
|
||||
links: %{
|
||||
"GitHub" =>
|
||||
"https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_nebulex",
|
||||
"OpenTelemetry Erlang" => "https://github.com/open-telemetry/opentelemetry-erlang",
|
||||
"OpenTelemetry Erlang Contrib" =>
|
||||
"https://github.com/open-telemetry/opentelemetry-erlang-contrib",
|
||||
"OpenTelemetry.io" => "https://opentelemetry.io"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
[
|
||||
extra_applications: []
|
||||
]
|
||||
end
|
||||
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
|
||||
{:ex_doc, "~> 0.28.0", only: [:dev], runtime: false},
|
||||
{:nebulex, "~> 2.1", only: [:dev, :test]},
|
||||
{:opentelemetry, "~> 1.0", only: [:dev, :test]},
|
||||
{:opentelemetry_api, "~> 1.0"},
|
||||
{:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]},
|
||||
{:opentelemetry_telemetry, "~> 1.0"},
|
||||
{:telemetry, "~> 0.4 or ~> 1.0"}
|
||||
]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
%{
|
||||
"acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"},
|
||||
"chatterbox": {:hex, :ts_chatterbox, "0.11.0", "b8f372c706023eb0de5bf2976764edb27c70fe67052c88c1f6a66b3a5626847f", [:rebar3], [{:hpack, "~>0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "722fe2bad52913ab7e87d849fc6370375f0c961ffb2f0b5e6d647c9170c382a6"},
|
||||
"ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
|
||||
"gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"},
|
||||
"grpcbox": {:hex, :grpcbox, "0.14.0", "3eb321bcd2275baf8b54cf381feb7b0559a50c02544de28fda039c7f2f9d1a7a", [:rebar3], [{:acceptor_pool, "~>1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~>0.11.0", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~>0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~>0.8.0", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "e24159b7b6d3f9869bbe528845c0125fed2259366ba908fd04a1f45fe81d0660"},
|
||||
"hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||
"nebulex": {:hex, :nebulex, "2.3.2", "74d8b54e867ca58930edf14de7cc35056cf1ae802bdbc64b3c8c5336234cb1ca", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.0", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "09e7c2e687a9d4da2cb6bdde90de74170450f17684f7cc5691faa213c5aa5b13"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
|
||||
"opentelemetry": {:hex, :opentelemetry, "1.0.3", "0d04f8f2c8b45c75cd7a6b31c0e3699f00bf82feee610f97f10971ddbcbb2010", [:rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "1e94db9989276f24c3ce9f0df2f46074a42f3f6c19057a2c1a6f863b6a1f1463"},
|
||||
"opentelemetry_api": {:hex, :opentelemetry_api, "1.0.3", "77f9644c42340cd8b18c728cde4822ed55ae136f0d07761b78e8c54da46af93a", [:mix, :rebar3], [], "hexpm", "4293e06bd369bc004e6fad5edbb56456d891f14bd3f9f1772b18f1923e0678ea"},
|
||||
"opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.0.4", "60a64c75633a82b6c36a20043be355ac72a7b9b21633edd47407924c5596dde0", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.11", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "61da65290fbb6cac3459b84b8cd630795bf608df93a2b2cc49251cae78200e5e"},
|
||||
"opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.0.0", "d5982a319e725fcd2305b306b65c18a86afdcf7d96821473cf0649ff88877615", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.3.0", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "3401d13a1d4b7aa941a77e6b3ec074f0ae77f83b5b2206766ce630123a9291a9"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
|
||||
"telemetry_registry": {:hex, :telemetry_registry, "0.3.0", "6768f151ea53fc0fbca70dbff5b20a8d663ee4e0c0b2ae589590e08658e76f1e", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e2adbc609f3e79ece7f29fec363a97a2c484ac78a83098535d6564781e917"},
|
||||
"tls_certificate_check": {:hex, :tls_certificate_check, "1.14.0", "6d1638d56ac68b25c987d401dffb7cd059281339aadc3f8bf27ab33ee19ddbfe", [:rebar3], [{:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "b4452ddd3ae89cd84451afa0e218cb3ccd5178fe3c1de7fabcbddb12a137bcf4"},
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
defmodule OpentelemetryNebulexTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
doctest OpentelemetryNebulex
|
||||
|
||||
require OpenTelemetry.Tracer
|
||||
require OpenTelemetry.Span
|
||||
require Record
|
||||
|
||||
for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do
|
||||
Record.defrecord(name, spec)
|
||||
end
|
||||
|
||||
for {name, spec} <- Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do
|
||||
Record.defrecord(name, spec)
|
||||
end
|
||||
|
||||
defmodule Local do
|
||||
use Nebulex.Cache,
|
||||
otp_app: :opentelemetry_nebulex,
|
||||
adapter: Nebulex.Adapters.Local
|
||||
end
|
||||
|
||||
defmodule Partitioned do
|
||||
use Nebulex.Cache,
|
||||
otp_app: :opentelemetry_nebulex,
|
||||
adapter: Nebulex.Adapters.Partitioned
|
||||
end
|
||||
|
||||
defmodule Multilevel do
|
||||
use Nebulex.Cache,
|
||||
otp_app: :opentelemetry_nebulex,
|
||||
adapter: Nebulex.Adapters.Multilevel
|
||||
|
||||
defmodule L1 do
|
||||
use Nebulex.Cache,
|
||||
otp_app: :opentelemetry_nebulex,
|
||||
adapter: Nebulex.Adapters.Local
|
||||
end
|
||||
|
||||
defmodule L2 do
|
||||
use Nebulex.Cache,
|
||||
otp_app: :opentelemetry_nebulex,
|
||||
adapter: Nebulex.Adapters.Partitioned
|
||||
end
|
||||
end
|
||||
|
||||
setup do
|
||||
:otel_simple_processor.set_exporter(:otel_exporter_pid, self())
|
||||
|
||||
OpenTelemetry.Tracer.start_span("test")
|
||||
|
||||
on_exit(fn ->
|
||||
OpenTelemetry.Tracer.end_span()
|
||||
end)
|
||||
end
|
||||
|
||||
test "local cache commands" do
|
||||
OpentelemetryNebulex.setup([:opentelemetry_nebulex_test, :local])
|
||||
|
||||
start_supervised!(Local)
|
||||
|
||||
# miss
|
||||
Local.get(:my_key)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :miss,
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Local
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
# write
|
||||
Local.put(:my_key, 42)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex put", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Local
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
# hit
|
||||
Local.get(:my_key)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :hit,
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Local
|
||||
} = :otel_attributes.map(attributes)
|
||||
end
|
||||
|
||||
test "partitioned cache commands" do
|
||||
OpentelemetryNebulex.setup([:opentelemetry_nebulex_test, :partitioned])
|
||||
OpentelemetryNebulex.setup([:opentelemetry_nebulex_test, :partitioned, :primary])
|
||||
|
||||
start_supervised!(Partitioned)
|
||||
|
||||
# miss
|
||||
Partitioned.get(:my_key)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :miss,
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Partitioned.Primary
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :miss,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Partitioned,
|
||||
"nebulex.keyslot": Nebulex.Adapters.Partitioned
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
# write
|
||||
Partitioned.put(:my_key, 42)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex put", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Partitioned.Primary
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex put", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Partitioned,
|
||||
"nebulex.keyslot": Nebulex.Adapters.Partitioned
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
# hit
|
||||
Partitioned.get(:my_key)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :hit,
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Partitioned.Primary
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :hit,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Partitioned,
|
||||
"nebulex.keyslot": Nebulex.Adapters.Partitioned
|
||||
} = :otel_attributes.map(attributes)
|
||||
end
|
||||
|
||||
test "multi-level cache commands" do
|
||||
OpentelemetryNebulex.setup([:opentelemetry_nebulex_test, :multilevel])
|
||||
OpentelemetryNebulex.setup([:opentelemetry_nebulex_test, :multilevel, :l1])
|
||||
OpentelemetryNebulex.setup([:opentelemetry_nebulex_test, :multilevel, :l2])
|
||||
|
||||
start_supervised!(
|
||||
{Multilevel,
|
||||
[
|
||||
levels: [
|
||||
{Multilevel.L1, []},
|
||||
{Multilevel.L2, []}
|
||||
]
|
||||
]}
|
||||
)
|
||||
|
||||
# write
|
||||
Multilevel.put(:my_key, 42)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex put", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Multilevel.L1
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex put", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Multilevel.L2,
|
||||
"nebulex.keyslot": Nebulex.Adapters.Partitioned
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex put", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Multilevel
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
# hit
|
||||
Multilevel.get(:my_key)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :hit,
|
||||
"nebulex.backend": :ets,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Multilevel.L1
|
||||
} = :otel_attributes.map(attributes)
|
||||
|
||||
assert_receive {:span, span(name: "nebulex get", kind: :internal, attributes: attributes)}
|
||||
|
||||
assert %{
|
||||
"nebulex.action": :hit,
|
||||
"nebulex.cache": OpentelemetryNebulexTest.Multilevel,
|
||||
"nebulex.model": :inclusive
|
||||
} = :otel_attributes.map(attributes)
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
ExUnit.start(capture_log: true)
|
Loading…
Reference in New Issue