Add instrumentation for remoulade (#1082)

This commit is contained in:
ben-natan
2022-05-31 23:05:41 +02:00
committed by GitHub
parent c6f9dcd359
commit 5f7c293e0f
18 changed files with 631 additions and 5 deletions

View File

@ -1,5 +1,9 @@
components:
instrumentation/opentelemetry-instrumentation-boto3sqs:
- oxeye-nikolay
- nikosokolik
instrumentation/opentelemetry-instrumentation-kafka-python:
- nozik
@ -7,9 +11,9 @@ components:
- oxeye-nikolay
- nikosokolik
instrumentation/opentelemetry-instrumentation-boto3sqs:
- oxeye-nikolay
- nikosokolik
instrumentation/opentelemetry-instrumentation-remoulade:
- ben-natan
- machine424
propagator/opentelemetry-propagator-aws-xray:
- NathanielRN

View File

@ -13,10 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- cleanup type hints for textmap `Getter` and `Setter` classes
([1106](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1106))
### Added
- `opentelemetry-instrumentation-remoulade` Initial release
([#1082](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1082))
## [1.12.0rc1-0.31b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc1-0.31b0) - 2022-05-17
### Fixed
- `opentelemetry-instrumentation-aiohttp-client` make span attributes available to sampler
([1072](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1072))

View File

@ -33,6 +33,7 @@ pymongo~=3.1
PyMySQL~=0.9.3
pyramid>=1.7
redis>=2.6
remoulade>=0.50
sqlalchemy>=1.0
tornado>=5.1.1
ddtrace>=0.34.0

View File

@ -29,6 +29,7 @@
| [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 |
| [opentelemetry-instrumentation-pyramid](./opentelemetry-instrumentation-pyramid) | pyramid >= 1.7 |
| [opentelemetry-instrumentation-redis](./opentelemetry-instrumentation-redis) | redis >= 2.6 |
| [opentelemetry-instrumentation-remoulade](./opentelemetry-instrumentation-remoulade) | remoulade >= 0.50 |
| [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 |
| [opentelemetry-instrumentation-sklearn](./opentelemetry-instrumentation-sklearn) | scikit-learn ~= 0.24.0 |
| [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy |

View File

@ -0,0 +1,9 @@
graft src
graft tests
global-exclude *.pyc
global-exclude *.pyo
global-exclude __pycache__/*
include CHANGELOG.md
include MANIFEST.in
include README.rst
include LICENSE

View File

@ -0,0 +1,23 @@
OpenTelemetry Remoulade Instrumentation
=======================================
|pypi|
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-remoulade.svg
:target: https://pypi.org/project/opentelemetry-instrumentation-remoulade/
This library allows tracing requests made by the Remoulade library.
Installation
------------
::
pip install opentelemetry-instrumentation-remoulade
References
----------
* `OpenTelemetry Remoulade Instrumentation <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/remoulade/remoulade.html>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_
* `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_

View File

@ -0,0 +1,56 @@
# Copyright The OpenTelemetry Authors
#
# 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.
#
[metadata]
name = opentelemetry-instrumentation-remoulade
description = OpenTelemetry Remoulade instrumentation
long_description = file: README.rst
long_description_content_type = text/x-rst
author = OpenTelemetry Authors
author_email = cncf-opentelemetry-contributors@lists.cncf.io
url = https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-remoulade
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 4 - Beta
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
[options]
python_requires = >=3.7
package_dir=
=src
packages=find_namespace:
install_requires =
opentelemetry-api ~= 1.10
opentelemetry-semantic-conventions == 0.31b0
opentelemetry-instrumentation == 0.31b0
[options.extras_require]
test =
opentelemetry-test-utils == 0.31b0
opentelemetry-sdk ~= 1.10
[options.packages.find]
where = src
[options.entry_points]
opentelemetry_instrumentor =
remoulade = opentelemetry.instrumentation.remoulade:RemouladeInstrumentor

View File

@ -0,0 +1,99 @@
# Copyright The OpenTelemetry Authors
#
# 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.
# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM templates/instrumentation_setup.py.txt.
# RUN `python scripts/generate_setup.py` TO REGENERATE.
import distutils.cmd
import json
import os
from configparser import ConfigParser
import setuptools
config = ConfigParser()
config.read("setup.cfg")
# We provide extras_require parameter to setuptools.setup later which
# overwrites the extras_require section from setup.cfg. To support extras_require
# section in setup.cfg, we load it here and merge it with the extras_require param.
extras_require = {}
if "options.extras_require" in config:
for key, value in config["options.extras_require"].items():
extras_require[key] = [v for v in value.split("\n") if v.strip()]
BASE_DIR = os.path.dirname(__file__)
PACKAGE_INFO = {}
VERSION_FILENAME = os.path.join(
BASE_DIR,
"src",
"opentelemetry",
"instrumentation",
"remoulade",
"version.py",
)
with open(VERSION_FILENAME, encoding="utf-8") as f:
exec(f.read(), PACKAGE_INFO)
PACKAGE_FILENAME = os.path.join(
BASE_DIR,
"src",
"opentelemetry",
"instrumentation",
"remoulade",
"package.py",
)
with open(PACKAGE_FILENAME, encoding="utf-8") as f:
exec(f.read(), PACKAGE_INFO)
# Mark any instruments/runtime dependencies as test dependencies as well.
extras_require["instruments"] = PACKAGE_INFO["_instruments"]
test_deps = extras_require.get("test", [])
for dep in extras_require["instruments"]:
test_deps.append(dep)
extras_require["test"] = test_deps
class JSONMetadataCommand(distutils.cmd.Command):
description = (
"print out package metadata as JSON. This is used by OpenTelemetry dev scripts to ",
"auto-generate code in other places",
)
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
metadata = {
"name": config["metadata"]["name"],
"version": PACKAGE_INFO["__version__"],
"instruments": PACKAGE_INFO["_instruments"],
}
print(json.dumps(metadata))
setuptools.setup(
cmdclass={"meta": JSONMetadataCommand},
version=PACKAGE_INFO["__version__"],
extras_require=extras_require,
)

View File

@ -0,0 +1,185 @@
# Copyright The OpenTelemetry Authors
#
# 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.
"""
Usage
-----
* Start broker backend
::
docker run -p 5672:5672 rabbitmq
* Run instrumented actor
.. code-block:: python
from remoulade.brokers.rabbitmq import RabbitmqBroker
import remoulade
RemouladeInstrumentor().instrument()
broker = RabbitmqBroker()
remoulade.set_broker(broker)
@remoulade.actor
def multiply(x, y):
return x * y
broker.declare_actor(count_words)
multiply.send(43, 51)
"""
from typing import Collection
from remoulade import Middleware, broker
from opentelemetry import trace
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.remoulade import utils
from opentelemetry.instrumentation.remoulade.package import _instruments
from opentelemetry.instrumentation.remoulade.version import __version__
from opentelemetry.propagate import extract, inject
from opentelemetry.semconv.trace import SpanAttributes
_REMOULADE_MESSAGE_TAG_KEY = "remoulade.action"
_REMOULADE_MESSAGE_SEND = "send"
_REMOULADE_MESSAGE_RUN = "run"
_REMOULADE_MESSAGE_NAME_KEY = "remoulade.actor_name"
_REMOULADE_MESSAGE_RETRY_COUNT_KEY = "remoulade.retry_count"
class _InstrumentationMiddleware(Middleware):
def __init__(self, _tracer):
self._tracer = _tracer
self._span_registry = {}
def before_process_message(self, _broker, message):
if "trace_ctx" not in message.options:
return
trace_ctx = extract(message.options["trace_ctx"])
retry_count = message.options.get("retries", 0)
operation_name = utils.get_operation_name(
"before_process_message", retry_count
)
span_attributes = {_REMOULADE_MESSAGE_RETRY_COUNT_KEY: retry_count}
span = self._tracer.start_span(
operation_name,
kind=trace.SpanKind.CONSUMER,
context=trace_ctx,
attributes=span_attributes,
)
activation = trace.use_span(span, end_on_exit=True)
activation.__enter__() # pylint: disable=E1101
utils.attach_span(
self._span_registry, message.message_id, (span, activation)
)
def after_process_message(
self, _broker, message, *, result=None, exception=None
):
span, activation = utils.retrieve_span(
self._span_registry, message.message_id
)
if span is None:
# no existing span found for message_id
return
if span.is_recording():
span.set_attributes(
{
_REMOULADE_MESSAGE_TAG_KEY: _REMOULADE_MESSAGE_RUN,
_REMOULADE_MESSAGE_NAME_KEY: message.actor_name,
SpanAttributes.MESSAGING_MESSAGE_ID: message.message_id,
}
)
activation.__exit__(None, None, None)
utils.detach_span(self._span_registry, message.message_id)
def before_enqueue(self, _broker, message, delay):
retry_count = message.options.get("retries", 0)
operation_name = utils.get_operation_name(
"before_enqueue", retry_count
)
span_attributes = {_REMOULADE_MESSAGE_RETRY_COUNT_KEY: retry_count}
span = self._tracer.start_span(
operation_name,
kind=trace.SpanKind.PRODUCER,
attributes=span_attributes,
)
if span.is_recording():
span.set_attributes(
{
_REMOULADE_MESSAGE_TAG_KEY: _REMOULADE_MESSAGE_SEND,
_REMOULADE_MESSAGE_NAME_KEY: message.actor_name,
SpanAttributes.MESSAGING_MESSAGE_ID: message.message_id,
}
)
activation = trace.use_span(span, end_on_exit=True)
activation.__enter__() # pylint: disable=E1101
utils.attach_span(
self._span_registry,
message.message_id,
(span, activation),
is_publish=True,
)
if "trace_ctx" not in message.options:
message.options["trace_ctx"] = {}
inject(message.options["trace_ctx"])
def after_enqueue(self, _broker, message, delay, exception=None):
_, activation = utils.retrieve_span(
self._span_registry, message.message_id, is_publish=True
)
if activation is None:
# no existing span found for message_id
return
activation.__exit__(None, None, None)
utils.detach_span(
self._span_registry, message.message_id, is_publish=True
)
class RemouladeInstrumentor(BaseInstrumentor):
def instrumentation_dependencies(self) -> Collection[str]:
return _instruments
def _instrument(self, **kwargs):
tracer_provider = kwargs.get("tracer_provider")
# pylint: disable=attribute-defined-outside-init
self._tracer = trace.get_tracer(__name__, __version__, tracer_provider)
instrumentation_middleware = _InstrumentationMiddleware(self._tracer)
broker.add_extra_default_middleware(instrumentation_middleware)
def _uninstrument(self, **kwargs):
broker.remove_extra_default_middleware(_InstrumentationMiddleware)

View File

@ -0,0 +1,15 @@
# Copyright The OpenTelemetry Authors
#
# 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.
_instruments = ("remoulade >= 0.50",)

View File

@ -0,0 +1,43 @@
# Copyright The OpenTelemetry Authors
#
# 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.
def attach_span(
span_registry, message_id, span_and_activation, is_publish=False
):
span_registry[(message_id, is_publish)] = span_and_activation
def detach_span(span_registry, message_id, is_publish=False):
span_registry.pop((message_id, is_publish))
def retrieve_span(span_registry, message_id, is_publish=False):
return span_registry.get((message_id, is_publish), (None, None))
def get_operation_name(hook_name, retry_count):
if hook_name == "before_process_message":
return (
"remoulade/process"
if retry_count == 0
else f"remoulade/process(retry-{retry_count})"
)
if hook_name == "before_enqueue":
return (
"remoulade/send"
if retry_count == 0
else f"remoulade/send(retry-{retry_count})"
)
return ""

View File

@ -0,0 +1,15 @@
# Copyright The OpenTelemetry Authors
#
# 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.
__version__ = "0.31b0"

View File

@ -0,0 +1,100 @@
# Copyright The OpenTelemetry Authors
#
# 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.
import remoulade
from remoulade.brokers.local import LocalBroker
from opentelemetry.instrumentation.remoulade import RemouladeInstrumentor
from opentelemetry.test.test_base import TestBase
from opentelemetry.trace import SpanKind
@remoulade.actor(max_retries=3)
def actor_div(dividend, divisor):
return dividend / divisor
class TestRemouladeInstrumentation(TestBase):
def setUp(self):
super().setUp()
RemouladeInstrumentor().instrument()
broker = LocalBroker()
remoulade.set_broker(broker)
broker.declare_actor(actor_div)
def test_message(self):
actor_div.send(2, 3)
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
self.assertEqual(len(spans), 2)
consumer, producer = spans
self.assertEqual(consumer.name, "remoulade/process")
self.assertEqual(consumer.kind, SpanKind.CONSUMER)
self.assertSpanHasAttributes(
consumer,
{
"remoulade.action": "run",
"remoulade.actor_name": "actor_div",
},
)
self.assertEqual(producer.name, "remoulade/send")
self.assertEqual(producer.kind, SpanKind.PRODUCER)
self.assertSpanHasAttributes(
producer,
{
"remoulade.action": "send",
"remoulade.actor_name": "actor_div",
},
)
self.assertNotEqual(consumer.parent, producer.context)
self.assertEqual(consumer.parent.span_id, producer.context.span_id)
self.assertEqual(consumer.context.trace_id, producer.context.trace_id)
def test_retries(self):
try:
actor_div.send(1, 0)
except ZeroDivisionError:
pass
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
self.assertEqual(len(spans), 8)
consumer_spans = spans[::2]
producer_spans = spans[1::2]
self.assertEqual(consumer_spans[0].name, "remoulade/process(retry-3)")
self.assertSpanHasAttributes(
consumer_spans[0], {"remoulade.retry_count": 3}
)
self.assertEqual(consumer_spans[1].name, "remoulade/process(retry-2)")
self.assertSpanHasAttributes(
consumer_spans[1], {"remoulade.retry_count": 2}
)
self.assertEqual(consumer_spans[3].name, "remoulade/process")
self.assertEqual(producer_spans[0].name, "remoulade/send(retry-3)")
self.assertSpanHasAttributes(
producer_spans[0], {"remoulade.retry_count": 3}
)
self.assertEqual(producer_spans[1].name, "remoulade/send(retry-2)")
self.assertSpanHasAttributes(
producer_spans[1], {"remoulade.retry_count": 2}
)
self.assertEqual(producer_spans[3].name, "remoulade/send")

View File

@ -0,0 +1,57 @@
# Copyright The OpenTelemetry Authors
#
# 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.
import remoulade
from remoulade.brokers.local import LocalBroker
from opentelemetry.instrumentation.remoulade import RemouladeInstrumentor
from opentelemetry.test.test_base import TestBase
@remoulade.actor(max_retries=3)
def actor_div(dividend, divisor):
return dividend / divisor
class TestRemouladeUninstrumentation(TestBase):
def setUp(self):
super().setUp()
RemouladeInstrumentor().instrument()
broker = LocalBroker()
remoulade.set_broker(broker)
broker.declare_actor(actor_div)
RemouladeInstrumentor().uninstrument()
def test_uninstrument_existing_broker(self):
actor_div.send(1, 1)
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
self.assertEqual(len(spans), 0)
def test_uninstrument_new_brokers(self):
new_broker = LocalBroker()
remoulade.set_broker(new_broker)
new_broker.declare_actor(actor_div)
actor_div.send(1, 1)
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
self.assertEqual(len(spans), 0)
def test_reinstrument_existing_broker(self):
RemouladeInstrumentor().instrument()
actor_div.send(1, 1)
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
self.assertEqual(len(spans), 2)

View File

@ -56,6 +56,7 @@ install_requires =
opentelemetry-instrumentation-pymysql==0.31b0
opentelemetry-instrumentation-pyramid==0.31b0
opentelemetry-instrumentation-redis==0.31b0
opentelemetry-instrumentation-remoulade==0.31b0
opentelemetry-instrumentation-requests==0.31b0
opentelemetry-instrumentation-sklearn==0.31b0
opentelemetry-instrumentation-sqlalchemy==0.31b0

View File

@ -116,6 +116,10 @@ libraries = {
"library": "redis >= 2.6",
"instrumentation": "opentelemetry-instrumentation-redis==0.31b0",
},
"remoulade": {
"library": "remoulade >= 0.50",
"instrumentation": "opentelemetry-instrumentation-remoulade==0.31b0",
},
"requests": {
"library": "requests ~= 2.0",
"instrumentation": "opentelemetry-instrumentation-requests==0.31b0",

11
tox.ini
View File

@ -153,6 +153,11 @@ envlist =
py3{6,7,8,9,10}-test-instrumentation-redis
pypy3-test-instrumentation-redis
; opentelemetry-instrumentation-remoulade
; remoulade only supports 3.7 and above
py3{7,8,9,10}-test-instrumentation-remoulade
; instrumentation-remoulade intentionally excluded from pypy3
; opentelemetry-instrumentation-celery
py3{6,7,8,9,10}-test-instrumentation-celery
pypy3-test-instrumentation-celery
@ -275,6 +280,7 @@ changedir =
test-instrumentation-pymysql: instrumentation/opentelemetry-instrumentation-pymysql/tests
test-instrumentation-pyramid: instrumentation/opentelemetry-instrumentation-pyramid/tests
test-instrumentation-redis: instrumentation/opentelemetry-instrumentation-redis/tests
test-instrumentation-remoulade: instrumentation/opentelemetry-instrumentation-remoulade/tests
test-instrumentation-requests: instrumentation/opentelemetry-instrumentation-requests/tests
test-instrumentation-sklearn: instrumentation/opentelemetry-instrumentation-sklearn/tests
test-instrumentation-sqlalchemy{11,14}: instrumentation/opentelemetry-instrumentation-sqlalchemy/tests
@ -354,6 +360,8 @@ commands_pre =
redis: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-redis[test]
remoulade: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-remoulade[test]
requests: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-requests[test]
starlette: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-starlette[test]
@ -448,6 +456,7 @@ commands_pre =
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pika[test]
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn[test]
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-redis[test]
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-remoulade[test]
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-fastapi[test]
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-jinja2[test]
python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-kafka-python[test]
@ -497,6 +506,7 @@ deps =
requests==2.25.0
pyodbc~=4.0.30
flaky==3.7.0
remoulade>=0.50
changedir =
tests/opentelemetry-docker-tests/tests
@ -519,6 +529,7 @@ commands_pre =
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy \
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-aiopg \
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-redis \
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-remoulade \
"{env:CORE_REPO}#egg=opentelemetry-exporter-opencensus&subdirectory=exporter/opentelemetry-exporter-opencensus"
docker-compose up -d
python check_availability.py