From 1de26cce1a0e5c0c2258f2d28222d0dbef3767e4 Mon Sep 17 00:00:00 2001 From: Maarten van Vliet Date: Wed, 5 Apr 2023 18:34:49 +0200 Subject: [PATCH] Add Dataloader instrumentation library (#137) * Add Dataloader instrumentation library * Move call to set context to @run_start event --------- Co-authored-by: Tristan Sloughter --- .github/workflows/elixir.yml | 45 +++++++++ .../opentelemetry_dataloader/.formatter.exs | 4 + .../opentelemetry_dataloader/.gitignore | 26 +++++ .../opentelemetry_dataloader/README.md | 27 +++++ .../config/config.exs | 34 +++++++ .../opentelemetry_dataloader/config/test.exs | 14 +++ .../docker-compose.yml | 11 +++ .../lib/opentelemetry_dataloader.ex | 99 +++++++++++++++++++ .../opentelemetry_dataloader/mix.exs | 62 ++++++++++++ .../opentelemetry_dataloader/mix.lock | 32 ++++++ .../test_repo/migrations/1_setup_tables.exs | 9 ++ .../test/opentelemetry_dataloader_test.exs | 69 +++++++++++++ .../test/support/models/post.ex | 7 ++ .../test/support/test_repo.ex | 5 + .../test/test_helper.exs | 5 + 15 files changed, 449 insertions(+) create mode 100644 instrumentation/opentelemetry_dataloader/.formatter.exs create mode 100644 instrumentation/opentelemetry_dataloader/.gitignore create mode 100644 instrumentation/opentelemetry_dataloader/README.md create mode 100644 instrumentation/opentelemetry_dataloader/config/config.exs create mode 100644 instrumentation/opentelemetry_dataloader/config/test.exs create mode 100644 instrumentation/opentelemetry_dataloader/docker-compose.yml create mode 100644 instrumentation/opentelemetry_dataloader/lib/opentelemetry_dataloader.ex create mode 100644 instrumentation/opentelemetry_dataloader/mix.exs create mode 100644 instrumentation/opentelemetry_dataloader/mix.lock create mode 100644 instrumentation/opentelemetry_dataloader/priv/test_repo/migrations/1_setup_tables.exs create mode 100644 instrumentation/opentelemetry_dataloader/test/opentelemetry_dataloader_test.exs create mode 100644 instrumentation/opentelemetry_dataloader/test/support/models/post.ex create mode 100644 instrumentation/opentelemetry_dataloader/test/support/test_repo.ex create mode 100644 instrumentation/opentelemetry_dataloader/test/test_helper.exs diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 51556cd..24c9521 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -344,7 +344,52 @@ jobs: run: mix format --check-formatted - name: Test run: mix test + opentelemetry-dataloader: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_dataloader')) + env: + app: 'opentelemetry_dataloader' + defaults: + run: + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-20.04 + name: Opentelemetry Dataloader test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} + services: + postgres: + image: circleci/postgres:13.3-ram + ports: ['5432:5432'] + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: opentelemetry_dataloader_test + steps: + - uses: actions/checkout@v3 + - 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@v3 + 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-process-propagator: needs: [test-matrix] if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_process_propagator')) diff --git a/instrumentation/opentelemetry_dataloader/.formatter.exs b/instrumentation/opentelemetry_dataloader/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/instrumentation/opentelemetry_dataloader/.gitignore b/instrumentation/opentelemetry_dataloader/.gitignore new file mode 100644 index 0000000..dd898de --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/.gitignore @@ -0,0 +1,26 @@ +# 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_dataloader-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/instrumentation/opentelemetry_dataloader/README.md b/instrumentation/opentelemetry_dataloader/README.md new file mode 100644 index 0000000..5e79bac --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/README.md @@ -0,0 +1,27 @@ +# OpentelemetryDataloader + +Telemetry handler that creates Opentelemetry spans from Dataloader events. + +After installing, setup the handler in your application behaviour before your +top-level supervisor starts. + +```elixir +OpentelemetryDataloader.setup() +``` + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `opentelemetry_dataloader` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:opentelemetry_dataloader, "~> 1.0.0"} + ] +end +``` + +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 . diff --git a/instrumentation/opentelemetry_dataloader/config/config.exs b/instrumentation/opentelemetry_dataloader/config/config.exs new file mode 100644 index 0000000..797ccd5 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/config/config.exs @@ -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_ecto, key: :value +# +# and access this configuration in your application as: +# +# Application.get_env(:opentelemetry_ecto, :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 diff --git a/instrumentation/opentelemetry_dataloader/config/test.exs b/instrumentation/opentelemetry_dataloader/config/test.exs new file mode 100644 index 0000000..218b4b0 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/config/test.exs @@ -0,0 +1,14 @@ +import Config + +config :opentelemetry_dataloader, + ecto_repos: [OpentelemetryDataloader.TestRepo] + +config :opentelemetry_dataloader, OpentelemetryDataloader.TestRepo, + hostname: "localhost", + username: "postgres", + password: "postgres", + database: "opentelemetry_dataloader_test", + pool: Ecto.Adapters.SQL.Sandbox + +config :opentelemetry, + processors: [{:otel_simple_processor, %{}}] diff --git a/instrumentation/opentelemetry_dataloader/docker-compose.yml b/instrumentation/opentelemetry_dataloader/docker-compose.yml new file mode 100644 index 0000000..4adc986 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.7" + +services: + postgres: + image: postgres:13.3 + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=opentelemetry_dataloader_test + ports: + - 5432:5432 diff --git a/instrumentation/opentelemetry_dataloader/lib/opentelemetry_dataloader.ex b/instrumentation/opentelemetry_dataloader/lib/opentelemetry_dataloader.ex new file mode 100644 index 0000000..238fb65 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/lib/opentelemetry_dataloader.ex @@ -0,0 +1,99 @@ +defmodule OpentelemetryDataloader do + @moduledoc """ + Module for automatic instrumentation of Dataloader. + + It works by listening to `[:dataloader, :source, :run/:batch, :start/:stop]` telemetry events. + """ + + @tracer_id __MODULE__ + + @run_start [:dataloader, :source, :run, :start] + @run_stop [:dataloader, :source, :run, :stop] + + @batch_start [:dataloader, :source, :batch, :run, :start] + @batch_stop [:dataloader, :source, :batch, :run, :stop] + + @doc """ + Initializes and configures the telemetry handlers. + """ + def setup(_opts \\ []) do + config = %{ + tracer_id: @tracer_id + } + + :telemetry.attach_many( + {__MODULE__, :run}, + [ + @run_start, + @run_stop + ], + &__MODULE__.handle_event/4, + config + ) + + :telemetry.attach_many( + {__MODULE__, :batch}, + [ + @batch_start, + @batch_stop + ], + &__MODULE__.handle_event/4, + config + ) + end + + def handle_event(event, measurements, metadata, config) + + def handle_event(@run_start, _measurements, metadata, config) do + parent_ctx = OpentelemetryProcessPropagator.fetch_parent_ctx(4, :"$callers") + + if parent_ctx != :undefined do + OpenTelemetry.Ctx.attach(parent_ctx) + end + + OpentelemetryTelemetry.start_telemetry_span( + config.tracer_id, + "dataloader.run", + metadata, + %{ + kind: :client + } + ) + end + + def handle_event(@run_stop, _measurements, metadata, config) do + OpentelemetryTelemetry.set_current_telemetry_span(config.tracer_id, metadata) + + OpentelemetryTelemetry.end_telemetry_span(config.tracer_id, metadata) + end + + def handle_event(@batch_start, _measurements, metadata, config) do + parent_ctx = OpentelemetryProcessPropagator.fetch_parent_ctx(4, :"$callers") + + if parent_ctx != :undefined do + OpenTelemetry.Ctx.attach(parent_ctx) + end + + {batch_name, _batch_args} = metadata.batch + + attributes = %{ + "dataloader.batch_key" => inspect(batch_name) + } + + OpentelemetryTelemetry.start_telemetry_span( + config.tracer_id, + "dataloader.batch", + metadata, + %{ + kind: :client, + attributes: attributes + } + ) + end + + def handle_event(@batch_stop, _measurements, metadata, config) do + OpentelemetryTelemetry.set_current_telemetry_span(config.tracer_id, metadata) + + OpentelemetryTelemetry.end_telemetry_span(config.tracer_id, metadata) + end +end diff --git a/instrumentation/opentelemetry_dataloader/mix.exs b/instrumentation/opentelemetry_dataloader/mix.exs new file mode 100644 index 0000000..f7c8457 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/mix.exs @@ -0,0 +1,62 @@ +defmodule OpentelemetryDataloader.MixProject do + use Mix.Project + + def project do + [ + app: :opentelemetry_dataloader, + description: "Trace Dataloader with OpenTelemetry.", + version: "1.0.0", + elixir: "~> 1.14", + start_permanent: Mix.env() == :prod, + deps: deps(), + aliases: aliases(), + elixirc_paths: elixirc_paths(Mix.env()), + package: package(), + source_url: + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_dataloader" + ] + 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_dataloader", + "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 + [] + end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + defp aliases() do + [test: ["ecto.drop -q", "ecto.create -q", "ecto.migrate --quiet", "test"]] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:telemetry, "~> 0.4 or ~> 1.0"}, + {:opentelemetry_api, "~> 1.0"}, + {:opentelemetry_telemetry, "~> 1.0"}, + {:dataloader, "~> 1.0.8"}, + {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, + {:opentelemetry, "~> 1.0", only: [:dev, :test]}, + {:ex_doc, "~> 0.29", only: [:dev], runtime: false}, + {:ecto_sql, ">= 3.0.0", only: [:dev, :test]}, + {:postgrex, ">= 0.15.0", only: [:dev, :test]}, + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, + {:opentelemetry_process_propagator, "~> 0.2"} + ] + end +end diff --git a/instrumentation/opentelemetry_dataloader/mix.lock b/instrumentation/opentelemetry_dataloader/mix.lock new file mode 100644 index 0000000..23a30d9 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/mix.lock @@ -0,0 +1,32 @@ +%{ + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "chatterbox": {:hex, :ts_chatterbox, "0.13.0", "6f059d97bcaa758b8ea6fffe2b3b81362bd06b639d3ea2bb088335511d691ebf", [:rebar3], [{:hpack, "~> 0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "b93d19104d86af0b3f2566c4cba2a57d2e06d103728246ba1ac6c3c0ff010aa7"}, + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, + "dataloader": {:hex, :dataloader, "1.0.10", "a42f07641b1a0572e0b21a2a5ae1be11da486a6790f3d0d14512d96ff3e3bbe9", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54cd70cec09addf4b2ace14cc186a283a149fd4d3ec5475b155951bf33cd963f"}, + "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, + "ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.1", "9bd5894eecc53d5b39d0c95180d4466aff00e10679e13a5cfa725f6f85c03c22", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fd470a4fff2e829bbf9dcceb7f3f9f6d1e49b4241e802f614de6b8b67c51118"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [: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", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, + "gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"}, + "grpcbox": {:hex, :grpcbox, "0.16.0", "b83f37c62d6eeca347b77f9b1ec7e9f62231690cdfeb3a31be07cd4002ba9c82", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.13.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", "294df743ae20a7e030889f00644001370a4f7ce0121f3bbdaf13cf3169c62913"}, + "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"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "opentelemetry": {:hex, :opentelemetry, "1.1.2", "77ba2fd2fee67bebde590851a4afeda45b3f298310aa410a2a3804b364cb598a", [:rebar3], [{:opentelemetry_api, "~> 1.1", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "5c60be189d6aed64a9fd17055f72c93eab144be441e625276c3c95533e6bb4c7"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.1.1", "3b43877c456c8a7f5448a95d9bf4fb4bb8cc2abbbea2c62d5f8e8c538b4af14f", [:mix, :rebar3], [], "hexpm", "a9554b3208b60a70043318d051ea78fbbc7a1b8f4c418ebc16ccb40015995675"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.2.2", "3966c56656627ef7db6c34c4ce28d44ac8629dcd065a310d7c33712fc2a1cfe3", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.1", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.1", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.11", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "5c11adeda19e0d203a04efe92cdd7a183da4b09ae8acaba7608e9303fa258e74"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.2.1", "20ac37648faf7175cade16fda8d58e6f1ff1b7f2a50a8ef9d70a032c41aba315", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "f317237e39636d4f6140afa5d419e85ed3dc9e9a57072e7cd442df42af7b8aac"}, + "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"}, + "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "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.16.0", "45b05e3b993dbace2e4ebccb666eadbd038f1da8f4db9691f4f34a274dfb0bd7", [:rebar3], [{:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3dc0508c749619b8d6a5e21aca4d719c184f065541795b0556398c8e574a3064"}, +} diff --git a/instrumentation/opentelemetry_dataloader/priv/test_repo/migrations/1_setup_tables.exs b/instrumentation/opentelemetry_dataloader/priv/test_repo/migrations/1_setup_tables.exs new file mode 100644 index 0000000..838832b --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/priv/test_repo/migrations/1_setup_tables.exs @@ -0,0 +1,9 @@ +defmodule OpentelemetryDataloader.TestRepo.Migrations.SetupTables do + use Ecto.Migration + + def change do + create table(:posts) do + add(:body, :text) + end + end +end diff --git a/instrumentation/opentelemetry_dataloader/test/opentelemetry_dataloader_test.exs b/instrumentation/opentelemetry_dataloader/test/opentelemetry_dataloader_test.exs new file mode 100644 index 0000000..8c35f5a --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/test/opentelemetry_dataloader_test.exs @@ -0,0 +1,69 @@ +defmodule OpentelemetryDataloaderTest do + use ExUnit.Case + + require OpenTelemetry.Tracer + + alias OpentelemetryDataloader.TestRepo, as: Repo + alias OpentelemetryDataloader.TestModels.Post + + require Record + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do + Record.defrecord(name, spec) + end + + setup do + :application.stop(:opentelemetry) + :application.set_env(:opentelemetry, :tracer, :otel_tracer_default) + + :application.set_env(:opentelemetry, :processors, [ + {:otel_batch_processor, %{scheduled_delay_ms: 1}} + ]) + + :application.start(:opentelemetry) + + :otel_batch_processor.set_exporter(:otel_exporter_pid, self()) + + OpenTelemetry.Tracer.start_span("test") + + on_exit(fn -> + OpenTelemetry.Tracer.end_span() + :telemetry.detach({__MODULE__, :batch}) + :telemetry.detach({__MODULE__, :run}) + end) + end + + test "captures dataloader ecto source events" do + OpentelemetryDataloader.setup() + + source = Dataloader.Ecto.new(Repo) + + loader = Dataloader.new() |> Dataloader.add_source(:db, source) + + loader = + loader + |> Dataloader.load(:db, Post, 1) + |> Dataloader.load_many(:db, Post, [4, 9]) + + Dataloader.run(loader) + + assert_receive {:span, + span( + name: "dataloader.run", + attributes: attributes, + kind: :client + )} + + assert %{} = :otel_attributes.map(attributes) + + assert_receive {:span, + span( + name: "dataloader.batch", + attributes: attributes, + kind: :client + )} + + assert %{"dataloader.batch_key" => key} = :otel_attributes.map(attributes) + assert key =~ ~r/OpentelemetryDataloader.TestModels.Post/ + end +end diff --git a/instrumentation/opentelemetry_dataloader/test/support/models/post.ex b/instrumentation/opentelemetry_dataloader/test/support/models/post.ex new file mode 100644 index 0000000..2de36d2 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/test/support/models/post.ex @@ -0,0 +1,7 @@ +defmodule OpentelemetryDataloader.TestModels.Post do + use Ecto.Schema + + schema "posts" do + field(:body, :string) + end +end diff --git a/instrumentation/opentelemetry_dataloader/test/support/test_repo.ex b/instrumentation/opentelemetry_dataloader/test/support/test_repo.ex new file mode 100644 index 0000000..be84ed2 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/test/support/test_repo.ex @@ -0,0 +1,5 @@ +defmodule OpentelemetryDataloader.TestRepo do + use Ecto.Repo, + otp_app: :opentelemetry_dataloader, + adapter: Ecto.Adapters.Postgres +end diff --git a/instrumentation/opentelemetry_dataloader/test/test_helper.exs b/instrumentation/opentelemetry_dataloader/test/test_helper.exs new file mode 100644 index 0000000..b326305 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/test/test_helper.exs @@ -0,0 +1,5 @@ +OpentelemetryDataloader.TestRepo.start_link() + +ExUnit.start(capture_log: true) + +Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryDataloader.TestRepo, {:shared, self()})