Andrew Rosa dfed96874f
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>
2022-12-14 17:38:18 -07:00

216 lines
6.4 KiB
Elixir

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