From a5a7f42baeb0fe63acdf031a4d109a75e9a657d9 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 15 Sep 2022 17:35:05 +0200 Subject: [PATCH 01/79] feat(instrumentation): create basic tracer and meter with console exporter --- extra-requirements.txt | 5 +++++ jina/__init__.py | 1 + jina/instrumentation/__init__.py | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 jina/instrumentation/__init__.py diff --git a/extra-requirements.txt b/extra-requirements.txt index 46d11f34ca21b..2dfbf8de327a5 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -35,8 +35,13 @@ packaging>=20.0: core docarray>=0.16.4: core jina-hubble-sdk>=0.15.1: core jcloud>=0.0.35: core +opentelemetry-api>=1.12.0: core uvloop: perf,standard,devel prometheus_client: perf,standard,devel +opentelemetry-sdk>=1.12.0: perf,standard,devel +opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel +opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel +opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel diff --git a/jina/__init__.py b/jina/__init__.py index 84ae1a5926434..0010b594328f2 100644 --- a/jina/__init__.py +++ b/jina/__init__.py @@ -102,6 +102,7 @@ def _warning_on_one_line(message, category, filename, lineno, *args, **kwargs): 'JINA_OPTOUT_TELEMETRY', 'JINA_RANDOM_PORT_MAX', 'JINA_RANDOM_PORT_MIN', + 'JINA_ENABLE_OTEL_TRACING', ) __default_host__ = _os.environ.get( diff --git a/jina/instrumentation/__init__.py b/jina/instrumentation/__init__.py new file mode 100644 index 0000000000000..e0ca83ecfdac8 --- /dev/null +++ b/jina/instrumentation/__init__.py @@ -0,0 +1,26 @@ +# These are the necessary import declarations +import os + +from opentelemetry import metrics, trace +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import ( + ConsoleMetricExporter, + PeriodicExportingMetricReader, +) +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter + +TRACER = trace.NoOpTracer +if 'JINA_ENABLE_OTEL_TRACING': + provider = TracerProvider() + processor = BatchSpanProcessor(ConsoleSpanExporter()) + provider.add_span_processor(processor) + trace.set_tracer_provider(provider) + + TRACER = trace.get_tracer(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) + +metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) +meter_provider = MeterProvider(metric_readers=[metric_reader]) +metrics.set_meter_provider(meter_provider) +# Sets the global meter provider +METER = metrics.get_meter(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) From 9e1b2d0dd0608324afb4075a46f0a0c5bb1f1552 Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Thu, 15 Sep 2022 15:41:09 +0000 Subject: [PATCH 02/79] style: fix overload and cli autocomplete --- jina/resources/extra-requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jina/resources/extra-requirements.txt b/jina/resources/extra-requirements.txt index 46d11f34ca21b..2dfbf8de327a5 100644 --- a/jina/resources/extra-requirements.txt +++ b/jina/resources/extra-requirements.txt @@ -35,8 +35,13 @@ packaging>=20.0: core docarray>=0.16.4: core jina-hubble-sdk>=0.15.1: core jcloud>=0.0.35: core +opentelemetry-api>=1.12.0: core uvloop: perf,standard,devel prometheus_client: perf,standard,devel +opentelemetry-sdk>=1.12.0: perf,standard,devel +opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel +opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel +opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel From 514792a02a3fb10ee3f797f87c5a3f2474ce2474 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 16 Sep 2022 10:06:39 +0200 Subject: [PATCH 03/79] feat(instrumentation): move the instrumentation package to the serve package --- jina/{ => serve}/instrumentation/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jina/{ => serve}/instrumentation/__init__.py (100%) diff --git a/jina/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py similarity index 100% rename from jina/instrumentation/__init__.py rename to jina/serve/instrumentation/__init__.py From c3b0c3714c7d33bda25dc6e73e91f35422bee981 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 16 Sep 2022 10:59:58 +0200 Subject: [PATCH 04/79] feat(instrumentation): provide options to enable tracing and metrics independently --- jina/serve/instrumentation/__init__.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index e0ca83ecfdac8..359146c159107 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -7,20 +7,30 @@ ConsoleMetricExporter, PeriodicExportingMetricReader, ) +from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter TRACER = trace.NoOpTracer +METER = metrics.NoOpMeter +resource = Resource( + attributes={SERVICE_NAME: os.getenv('JINA_DEPLOYMENT_NAME', 'worker')} +) + if 'JINA_ENABLE_OTEL_TRACING': provider = TracerProvider() processor = BatchSpanProcessor(ConsoleSpanExporter()) provider.add_span_processor(processor) trace.set_tracer_provider(provider) - TRACER = trace.get_tracer(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) +else: + trace.set_tracer_provider(TRACER) -metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) -meter_provider = MeterProvider(metric_readers=[metric_reader]) -metrics.set_meter_provider(meter_provider) -# Sets the global meter provider -METER = metrics.get_meter(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) +if 'JINA_ENABLE_OTEL_METRICS': + metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) + meter_provider = MeterProvider(metric_readers=[metric_reader]) + metrics.set_meter_provider(meter_provider) + # Sets the global meter provider + METER = metrics.get_meter(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) +else: + metrics.set_meter_provider(METER) From 2269b5726fadd7572d68af43d31837fdd5a5ffd6 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 19 Sep 2022 10:48:07 +0200 Subject: [PATCH 05/79] feat(instrumentation): add the correct grpc opentelmetery insturmentatio library --- extra-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/extra-requirements.txt b/extra-requirements.txt index 2dfbf8de327a5..3394f3221ad66 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -42,6 +42,7 @@ opentelemetry-sdk>=1.12.0: perf,standard,devel opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel +opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel From 14cb744fc0db4580efde347a7510c2b23053ea00 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 19 Sep 2022 16:27:59 +0200 Subject: [PATCH 06/79] feat(serve): instrument grpc server and channel with interceptors --- jina/serve/instrumentation/__init__.py | 14 +- jina/serve/instrumentation/_aio_client.py | 334 +++++++++++++++++++ jina/serve/instrumentation/_aio_server.py | 120 +++++++ jina/serve/networking.py | 57 +++- jina/serve/runtimes/gateway/grpc/__init__.py | 5 +- 5 files changed, 516 insertions(+), 14 deletions(-) create mode 100644 jina/serve/instrumentation/_aio_client.py create mode 100644 jina/serve/instrumentation/_aio_server.py diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 359146c159107..36c254661d07a 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -2,6 +2,9 @@ import os from opentelemetry import metrics, trace +from opentelemetry.instrumentation.grpc import ( + client_interceptor as grpc_client_interceptor, +) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import ( ConsoleMetricExporter, @@ -18,7 +21,7 @@ ) if 'JINA_ENABLE_OTEL_TRACING': - provider = TracerProvider() + provider = TracerProvider(resource=resource) processor = BatchSpanProcessor(ConsoleSpanExporter()) provider.add_span_processor(processor) trace.set_tracer_provider(provider) @@ -28,9 +31,16 @@ if 'JINA_ENABLE_OTEL_METRICS': metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) - meter_provider = MeterProvider(metric_readers=[metric_reader]) + meter_provider = MeterProvider(metric_readers=[metric_reader], resource=resource) metrics.set_meter_provider(meter_provider) # Sets the global meter provider METER = metrics.get_meter(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) else: metrics.set_meter_provider(METER) + + +def client_tracing_interceptor(): + ''' + :returns: a gRPC client interceptor with the global tracing provider. + ''' + return grpc_client_interceptor(trace.get_tracer_provider()) diff --git a/jina/serve/instrumentation/_aio_client.py b/jina/serve/instrumentation/_aio_client.py new file mode 100644 index 0000000000000..562033f455f67 --- /dev/null +++ b/jina/serve/instrumentation/_aio_client.py @@ -0,0 +1,334 @@ +# 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 functools +from collections import OrderedDict + +import grpc +from grpc.aio import ClientCallDetails +from opentelemetry import context, trace +from opentelemetry.instrumentation.grpc._client import ( + OpenTelemetryClientInterceptor, + _carrier_setter, +) +from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY +from opentelemetry.propagate import inject +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace.status import Status, StatusCode + +from jina.serve.instrumentation import TRACER + + +def _unary_done_callback(span, code, details): + def callback(call): + try: + span.set_attribute( + SpanAttributes.RPC_GRPC_STATUS_CODE, + code.value[0], + ) + if code != grpc.StatusCode.OK: + span.set_status( + Status( + status_code=StatusCode.ERROR, + description=details, + ) + ) + finally: + span.end() + + return callback + + +class _BaseAioClientInterceptor(OpenTelemetryClientInterceptor): + @staticmethod + def propagate_trace_in_details(client_call_details): + ''' + # noqa: DAR101 + # noqa: DAR201 + ''' + metadata = client_call_details.metadata + if not metadata: + mutable_metadata = OrderedDict() + else: + mutable_metadata = OrderedDict(metadata) + + inject(mutable_metadata, setter=_carrier_setter) + metadata = tuple(mutable_metadata.items()) + + return ClientCallDetails( + client_call_details.method, + client_call_details.timeout, + metadata, + client_call_details.credentials, + client_call_details.wait_for_ready, + ) + + @staticmethod + def add_error_details_to_span(span, exc): + if isinstance(exc, grpc.RpcError): + span.set_attribute( + SpanAttributes.RPC_GRPC_STATUS_CODE, + exc.code().value[0], + ) + span.set_status( + Status( + status_code=StatusCode.ERROR, + description=f"{type(exc).__name__}: {exc}", + ) + ) + span.record_exception(exc) + + async def _wrap_unary_response(self, continuation, span): + ''' + # noqa: DAR101 + # noqa: DAR201 + ''' + try: + call = await continuation() + + # code and details are both coroutines that need to be await-ed, + # the callbacks added with add_done_callback do not allow async + # code so we need to get the code and details here then pass them + # to the callback. + code = await call.code() + details = await call.details() + + call.add_done_callback(_unary_done_callback(span, code, details)) + + return call + except grpc.aio.AioRpcError as exc: + self.add_error_details_to_span(span, exc) + raise exc + + async def _wrap_stream_response(self, span, call): + try: + async for response in call: + yield response + except Exception as exc: + self.add_error_details_to_span(span, exc) + raise exc + finally: + span.end() + + +class UnaryUnaryAioClientInterceptor( + grpc.aio.UnaryUnaryClientInterceptor, + _BaseAioClientInterceptor, +): + '''Affords intercepting unary-unary invocations.''' + + async def intercept_unary_unary(self, continuation, client_call_details, request): + '''Intercepts a unary-unary invocation asynchronously. + + :param continuation: A coroutine that proceeds with the invocation by executing + the next interceptor in the chain or invoking the actual RPC on the + underlying Channel. It is the interceptor's responsibility to call it if + it decides to move the RPC forward. The interceptor can use + `call = await continuation(client_call_details, request)` to continue with + the RPC. `continuation` returns the call to the RPC. + :param client_call_details: A ClientCallDetails object describing the outgoing RPC. + :param request: The request value for the RPC. + + :returns: An object with the RPC response. + + :raises: AioRpcError: Indicating that the RPC terminated with non-OK status. + :raises: asyncio.CancelledError: Indicating that the RPC was canceled. + ''' + + if context.get_value(_SUPPRESS_INSTRUMENTATION_KEY): + return await continuation(client_call_details, request) + + method = client_call_details.method.decode("utf-8") + with self._start_span( + method, + end_on_exit=False, + record_exception=False, + set_status_on_exception=False, + ) as span: + new_details = self.propagate_trace_in_details(client_call_details) + + continuation_with_args = functools.partial( + continuation, new_details, request + ) + return await self._wrap_unary_response(continuation_with_args, span) + + +class UnaryStreamAioClientInterceptor( + grpc.aio.UnaryStreamClientInterceptor, + _BaseAioClientInterceptor, +): + '''Affords intercepting unary-stream invocations.''' + + async def intercept_unary_stream(self, continuation, client_call_details, request): + '''Intercepts a unary-stream invocation asynchronously. + + The function could return the call object or an asynchronous + iterator, in case of being an asyncrhonous iterator this will + become the source of the reads done by the caller. + + :param continuation: A coroutine that proceeds with the invocation by + executing the next interceptor in the chain or invoking the + actual RPC on the underlying Channel. It is the interceptor's + responsibility to call it if it decides to move the RPC forward. + The interceptor can use + `call = await continuation(client_call_details, request)` + to continue with the RPC. `continuation` returns the call to the + RPC. + :param client_call_details: A ClientCallDetails object describing the + outgoing RPC. + :param request: The request value for the RPC. + + :returns: The RPC Call or an asynchronous iterator. + + :raises: AioRpcError: Indicating that the RPC terminated with non-OK status. + :raises: asyncio.CancelledError: Indicating that the RPC was canceled. + ''' + + if context.get_value(_SUPPRESS_INSTRUMENTATION_KEY): + return await continuation(client_call_details, request) + + method = client_call_details.method.decode("utf-8") + with self._start_span( + method, + end_on_exit=False, + record_exception=False, + set_status_on_exception=False, + ) as span: + new_details = self.propagate_trace_in_details(client_call_details) + + resp = await continuation(new_details, request) + + return self._wrap_stream_response(span, resp) + + +class StreamUnaryAioClientInterceptor( + grpc.aio.StreamUnaryClientInterceptor, + _BaseAioClientInterceptor, +): + '''Affords intercepting stream-unary invocations.''' + + async def intercept_stream_unary( + self, continuation, client_call_details, request_iterator + ): + '''Intercepts a stream-unary invocation asynchronously. + + Within the interceptor the usage of the call methods like `write` or + even awaiting the call should be done carefully, since the caller + could be expecting an untouched call, for example for start writing + messages to it. + + :param continuation: A coroutine that proceeds with the invocation by + executing the next interceptor in the chain or invoking the + actual RPC on the underlying Channel. It is the interceptor's + responsibility to call it if it decides to move the RPC forward. + The interceptor can use + `call = await continuation(client_call_details, request_iterator)` + to continue with the RPC. `continuation` returns the call to the + RPC. + :param client_call_details: A ClientCallDetails object describing the + outgoing RPC. + :param request_iterator: The request iterator that will produce requests + for the RPC. + + :returns: The RPC Call. + + :raises: AioRpcError: Indicating that the RPC terminated with non-OK status. + :raises: asyncio.CancelledError: Indicating that the RPC was canceled. + ''' + + if context.get_value(_SUPPRESS_INSTRUMENTATION_KEY): + return await continuation(client_call_details, request_iterator) + + method = client_call_details.method.decode("utf-8") + with self._start_span( + method, + end_on_exit=False, + record_exception=False, + set_status_on_exception=False, + ) as span: + new_details = self.propagate_trace_in_details(client_call_details) + + continuation_with_args = functools.partial( + continuation, new_details, request_iterator + ) + return await self._wrap_unary_response(continuation_with_args, span) + + +class StreamStreamAioClientInterceptor( + grpc.aio.StreamStreamClientInterceptor, + _BaseAioClientInterceptor, +): + '''Affords intercepting stream-stream invocations.''' + + async def intercept_stream_stream( + self, continuation, client_call_details, request_iterator + ): + '''Intercepts a stream-stream invocation asynchronously. + + Within the interceptor the usage of the call methods like `write` or + even awaiting the call should be done carefully, since the caller + could be expecting an untouched call, for example for start writing + messages to it. + + The function could return the call object or an asynchronous + iterator, in case of being an asyncrhonous iterator this will + become the source of the reads done by the caller. + + :param continuation: A coroutine that proceeds with the invocation by + executing the next interceptor in the chain or invoking the + actual RPC on the underlying Channel. It is the interceptor's + responsibility to call it if it decides to move the RPC forward. + The interceptor can use + `call = await continuation(client_call_details, request_iterator)` + to continue with the RPC. `continuation` returns the call to the + RPC. + :param client_call_details: A ClientCallDetails object describing the + outgoing RPC. + :param request_iterator: The request iterator that will produce requests + for the RPC. + + :returns: The RPC Call or an asynchronous iterator. + + :raises: AioRpcError: Indicating that the RPC terminated with non-OK status. + :raises: asyncio.CancelledError: Indicating that the RPC was canceled. + ''' + + if context.get_value(_SUPPRESS_INSTRUMENTATION_KEY): + return await continuation(client_call_details, request_iterator) + + method = client_call_details.method.decode("utf-8") + with self._start_span( + method, + end_on_exit=False, + record_exception=False, + set_status_on_exception=False, + ) as span: + new_details = self.propagate_trace_in_details(client_call_details) + + resp = await continuation(new_details, request_iterator) + + return self._wrap_stream_response(span, resp) + + +def aio_client_interceptors(): + '''Create a gRPC client aio channel interceptor. + :returns: An invocation-side list of aio interceptor objects. + ''' + + return [ + UnaryUnaryAioClientInterceptor(TRACER), + UnaryStreamAioClientInterceptor(TRACER), + StreamUnaryAioClientInterceptor(TRACER), + StreamStreamAioClientInterceptor(TRACER), + ] diff --git a/jina/serve/instrumentation/_aio_server.py b/jina/serve/instrumentation/_aio_server.py new file mode 100644 index 0000000000000..673b3633dbb37 --- /dev/null +++ b/jina/serve/instrumentation/_aio_server.py @@ -0,0 +1,120 @@ +# 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 grpc.aio +from opentelemetry import trace +from opentelemetry.instrumentation.grpc._server import ( + OpenTelemetryServerInterceptor, + _OpenTelemetryServicerContext, + _wrap_rpc_behavior, +) + +from jina.serve.instrumentation import TRACER + + +class OpenTelemetryAioServerInterceptor( + grpc.aio.ServerInterceptor, OpenTelemetryServerInterceptor +): + ''' + An AsyncIO gRPC server interceptor, to add OpenTelemetry. + Usage:: + tracer = some OpenTelemetry tracer + interceptors = [ + AsyncOpenTelemetryServerInterceptor(tracer), + ] + server = aio.server( + futures.ThreadPoolExecutor(max_workers=concurrency), + interceptors = (interceptors,)) + ''' + + async def intercept_service(self, continuation, handler_call_details): + ''' + # noqa: DAR101 + # noqa: DAR102 + # noqa: DAR201 + ''' + + def telemetry_wrapper(behavior, request_streaming, response_streaming): + # handle streaming responses specially + if response_streaming: + return self._intercept_aio_server_stream( + behavior, + handler_call_details, + ) + + return self._intercept_aio_server_unary( + behavior, + handler_call_details, + ) + + next_handler = await continuation(handler_call_details) + + return _wrap_rpc_behavior(next_handler, telemetry_wrapper) + + def _intercept_aio_server_unary(self, behavior, handler_call_details): + async def _unary_interceptor(request_or_iterator, context): + with self._set_remote_context(context): + with self._start_span( + handler_call_details, + context, + set_status_on_exception=False, + ) as span: + # wrap the context + context = _OpenTelemetryServicerContext(context, span) + + # And now we run the actual RPC. + try: + return await behavior(request_or_iterator, context) + + except Exception as error: + # Bare exceptions are likely to be gRPC aborts, which + # we handle in our context wrapper. + # Here, we're interested in uncaught exceptions. + # pylint:disable=unidiomatic-typecheck + if type(error) != Exception: + span.record_exception(error) + raise error + + return _unary_interceptor + + def _intercept_aio_server_stream(self, behavior, handler_call_details): + async def _stream_interceptor(request_or_iterator, context): + with self._set_remote_context(context): + with self._start_span( + handler_call_details, + context, + set_status_on_exception=False, + ) as span: + context = _OpenTelemetryServicerContext(context, span) + + try: + async for response in behavior(request_or_iterator, context): + yield response + + except Exception as error: + # pylint:disable=unidiomatic-typecheck + if type(error) != Exception: + span.record_exception(error) + raise error + + return _stream_interceptor + + +def aio_server_interceptor(): + '''Create a gRPC aio server interceptor. + :returns: A service-side aio interceptor object. + ''' + from . import _aio_server + + return _aio_server.OpenTelemetryAioServerInterceptor(TRACER) diff --git a/jina/serve/networking.py b/jina/serve/networking.py index 1571cb1de164d..71865e545bb42 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -3,7 +3,7 @@ import ipaddress import os from collections import defaultdict -from typing import Dict, List, Optional, Set, Tuple, Union +from typing import Dict, List, Optional, Sequence, Set, Tuple, Union from urllib.parse import urlparse import grpc @@ -11,6 +11,7 @@ from grpc_health.v1 import health_pb2, health_pb2_grpc from grpc_reflection.v1alpha.reflection_pb2 import ServerReflectionRequest from grpc_reflection.v1alpha.reflection_pb2_grpc import ServerReflectionStub +from opentelemetry.instrumentation.grpc.grpcext import intercept_channel from jina import __default_endpoint__ from jina.enums import PollingType @@ -18,6 +19,8 @@ from jina.importer import ImportExtensions from jina.logging.logger import JinaLogger from jina.proto import jina_pb2, jina_pb2_grpc +from jina.serve.instrumentation import client_tracing_interceptor +from jina.serve.instrumentation._aio_client import aio_client_interceptors from jina.types.request import Request from jina.types.request.data import DataRequest @@ -899,6 +902,41 @@ async def task_wrapper(): return asyncio.create_task(task_wrapper()) + @staticmethod + def __aio_channel_with_tracing_interceptor( + address, + credentials=None, + options=None, + ) -> grpc.aio.Channel: + if credentials: + return grpc.aio.secure_channel( + address, + credentials, + options=options, + interceptors=aio_client_interceptors(), + ) + return grpc.aio.insecure_channel( + address, options=options, interceptors=aio_client_interceptors() + ) + + @staticmethod + def __channel_with_tracing_interceptor( + address, + credentials=None, + options=None, + ) -> grpc.Channel: + interceptor = client_tracing_interceptor() + print(f'--->type of interceptor: {type(interceptor)}') + if credentials: + return intercept_channel( + grpc.secure_channel(address, credentials, options=options), + interceptor, + ) + return intercept_channel( + grpc.insecure_channel(address, options=options), + interceptor, + ) + @staticmethod def get_grpc_channel( address: str, @@ -919,24 +957,23 @@ def get_grpc_channel( :return: A grpc channel or an asyncio channel """ - secure_channel = grpc.secure_channel - insecure_channel = grpc.insecure_channel - - if asyncio: - secure_channel = grpc.aio.secure_channel - insecure_channel = grpc.aio.insecure_channel - if options is None: options = GrpcConnectionPool.get_default_grpc_options() + credentials = None if tls: credentials = grpc.ssl_channel_credentials( root_certificates=root_certificates ) - return secure_channel(address, credentials, options) + if asyncio: + return GrpcConnectionPool.__aio_channel_with_tracing_interceptor( + address, credentials, options + ) - return insecure_channel(address, options) + return GrpcConnectionPool.__channel_with_tracing_interceptor( + address, credentials, options + ) @staticmethod def send_request_sync( diff --git a/jina/serve/runtimes/gateway/grpc/__init__.py b/jina/serve/runtimes/gateway/grpc/__init__.py index f2d62c7590986..6032b5a4a429f 100644 --- a/jina/serve/runtimes/gateway/grpc/__init__.py +++ b/jina/serve/runtimes/gateway/grpc/__init__.py @@ -10,6 +10,7 @@ from jina.helper import get_full_version, is_port_free from jina.proto import jina_pb2, jina_pb2_grpc from jina.serve.bff import GatewayBFF +from jina.serve.instrumentation._aio_server import aio_server_interceptor from jina.serve.runtimes.gateway import GatewayRuntime from jina.serve.runtimes.helper import _get_grpc_server_options from jina.types.request.status import StatusMessage @@ -46,9 +47,9 @@ async def async_setup(self): raise PortAlreadyUsed(f'port:{self.args.port}') self.server = grpc.aio.server( - options=_get_grpc_server_options(self.args.grpc_server_options) + options=_get_grpc_server_options(self.args.grpc_server_options), + interceptors=[aio_server_interceptor()], ) - await self._async_setup_server() async def _async_setup_server(self): From f53be22c8100fe79f9f63aeef6a126ebdd8d97ad Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Mon, 19 Sep 2022 14:29:23 +0000 Subject: [PATCH 07/79] style: fix overload and cli autocomplete --- jina/clients/__init__.py | 25 +- jina/orchestrate/flow/base.py | 370 +++++++++++++------------- jina/resources/extra-requirements.txt | 1 + 3 files changed, 188 insertions(+), 208 deletions(-) diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index aa7631654d9f3..24799c532ac25 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -16,23 +16,14 @@ # overload_inject_start_client @overload -def Client( - *, - asyncio: Optional[bool] = False, - host: Optional[str] = '0.0.0.0', - port: Optional[int] = None, - protocol: Optional[str] = 'GRPC', - proxy: Optional[bool] = False, - tls: Optional[bool] = False, - **kwargs -) -> Union[ - 'AsyncWebSocketClient', - 'WebSocketClient', - 'AsyncGRPCClient', - 'GRPCClient', - 'HTTPClient', - 'AsyncHTTPClient', -]: +def Client(*, + asyncio: Optional[bool] = False, + host: Optional[str] = '0.0.0.0', + port: Optional[int] = None, + protocol: Optional[str] = 'GRPC', + proxy: Optional[bool] = False, + tls: Optional[bool] = False, + **kwargs) -> Union['AsyncWebSocketClient', 'WebSocketClient', 'AsyncGRPCClient', 'GRPCClient', 'HTTPClient', 'AsyncHTTPClient']: """Create a Client. Client is how user interact with Flow :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index bc3aa16db58f2..0d9d02f982f74 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -111,16 +111,14 @@ class Flow( # overload_inject_start_client_flow @overload def __init__( - self, - *, - asyncio: Optional[bool] = False, - host: Optional[str] = '0.0.0.0', - port: Optional[int] = None, - protocol: Optional[str] = 'GRPC', - proxy: Optional[bool] = False, - tls: Optional[bool] = False, - **kwargs, - ): + self,*, + asyncio: Optional[bool] = False, + host: Optional[str] = '0.0.0.0', + port: Optional[int] = None, + protocol: Optional[str] = 'GRPC', + proxy: Optional[bool] = False, + tls: Optional[bool] = False, + **kwargs): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina client` CLI. :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. @@ -134,63 +132,60 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ - # overload_inject_end_client_flow # overload_inject_start_gateway_flow @overload def __init__( - self, - *, - compression: Optional[str] = None, - cors: Optional[bool] = False, - deployments_addresses: Optional[str] = '{}', - deployments_disable_reduce: Optional[str] = '[]', - description: Optional[str] = None, - env: Optional[dict] = None, - exit_on_exceptions: Optional[List[str]] = [], - expose_endpoints: Optional[str] = None, - expose_graphql_endpoint: Optional[bool] = False, - floating: Optional[bool] = False, - graph_conditions: Optional[str] = '{}', - graph_description: Optional[str] = '{}', - grpc_server_options: Optional[dict] = None, - host: Optional[str] = '0.0.0.0', - host_in: Optional[str] = '0.0.0.0', - log_config: Optional[str] = None, - monitoring: Optional[bool] = False, - name: Optional[str] = 'gateway', - native: Optional[bool] = False, - no_crud_endpoints: Optional[bool] = False, - no_debug_endpoints: Optional[bool] = False, - output_array_type: Optional[str] = None, - polling: Optional[str] = 'ANY', - port: Optional[int] = None, - port_monitoring: Optional[str] = None, - prefetch: Optional[int] = 1000, - protocol: Optional[str] = 'GRPC', - proxy: Optional[bool] = False, - py_modules: Optional[List[str]] = None, - quiet: Optional[bool] = False, - quiet_error: Optional[bool] = False, - replicas: Optional[int] = 1, - retries: Optional[int] = -1, - runtime_cls: Optional[str] = 'GRPCGatewayRuntime', - shards: Optional[int] = 1, - ssl_certfile: Optional[str] = None, - ssl_keyfile: Optional[str] = None, - timeout_ctrl: Optional[int] = 60, - timeout_ready: Optional[int] = 600000, - timeout_send: Optional[int] = None, - title: Optional[str] = None, - uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', - uses_metas: Optional[dict] = None, - uses_requests: Optional[dict] = None, - uses_with: Optional[dict] = None, - uvicorn_kwargs: Optional[dict] = None, - workspace: Optional[str] = None, - **kwargs, - ): + self,*, + compression: Optional[str] = None, + cors: Optional[bool] = False, + deployments_addresses: Optional[str] = '{}', + deployments_disable_reduce: Optional[str] = '[]', + description: Optional[str] = None, + env: Optional[dict] = None, + exit_on_exceptions: Optional[List[str]] = [], + expose_endpoints: Optional[str] = None, + expose_graphql_endpoint: Optional[bool] = False, + floating: Optional[bool] = False, + graph_conditions: Optional[str] = '{}', + graph_description: Optional[str] = '{}', + grpc_server_options: Optional[dict] = None, + host: Optional[str] = '0.0.0.0', + host_in: Optional[str] = '0.0.0.0', + log_config: Optional[str] = None, + monitoring: Optional[bool] = False, + name: Optional[str] = 'gateway', + native: Optional[bool] = False, + no_crud_endpoints: Optional[bool] = False, + no_debug_endpoints: Optional[bool] = False, + output_array_type: Optional[str] = None, + polling: Optional[str] = 'ANY', + port: Optional[int] = None, + port_monitoring: Optional[str] = None, + prefetch: Optional[int] = 1000, + protocol: Optional[str] = 'GRPC', + proxy: Optional[bool] = False, + py_modules: Optional[List[str]] = None, + quiet: Optional[bool] = False, + quiet_error: Optional[bool] = False, + replicas: Optional[int] = 1, + retries: Optional[int] = -1, + runtime_cls: Optional[str] = 'GRPCGatewayRuntime', + shards: Optional[int] = 1, + ssl_certfile: Optional[str] = None, + ssl_keyfile: Optional[str] = None, + timeout_ctrl: Optional[int] = 60, + timeout_ready: Optional[int] = 600000, + timeout_send: Optional[int] = None, + title: Optional[str] = None, + uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', + uses_metas: Optional[dict] = None, + uses_requests: Optional[dict] = None, + uses_with: Optional[dict] = None, + uvicorn_kwargs: Optional[dict] = None, + workspace: Optional[str] = None, + **kwargs): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina gateway` CLI. :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. @@ -211,22 +206,22 @@ def __init__( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param no_crud_endpoints: If set, `/index`, `/search`, `/update`, `/delete` endpoints are removed from HTTP interface. - + Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -239,13 +234,13 @@ def __init__( {'/custom': 'ALL', '/search': 'ANY', '*': 'ANY'} :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] - :param prefetch: Number of requests fetched from the client before feeding into the first Executor. - + :param prefetch: Number of requests fetched from the client before feeding into the first Executor. + Used to control the speed of data input into a Flow. 0 disables prefetch (1000 requests is the default) :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -269,7 +264,7 @@ def __init__( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -277,7 +272,7 @@ def __init__( :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server - + More details can be found in Uvicorn docs: https://www.uvicorn.org/settings/ :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. @@ -285,38 +280,35 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ - # overload_inject_end_gateway_flow # overload_inject_start_flow @overload def __init__( - self, - *, - env: Optional[dict] = None, - inspect: Optional[str] = 'COLLECT', - log_config: Optional[str] = None, - name: Optional[str] = None, - quiet: Optional[bool] = False, - quiet_error: Optional[bool] = False, - uses: Optional[str] = None, - workspace: Optional[str] = None, - **kwargs, - ): + self,*, + env: Optional[dict] = None, + inspect: Optional[str] = 'COLLECT', + log_config: Optional[str] = None, + name: Optional[str] = None, + quiet: Optional[bool] = False, + quiet_error: Optional[bool] = False, + uses: Optional[str] = None, + workspace: Optional[str] = None, + **kwargs): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina flow` CLI. :param env: The map of environment variables that are available inside runtime :param inspect: The strategy on those inspect deployments in the flow. - + If `REMOVE` is given then all inspect deployments are removed when building the flow. :param log_config: The YAML config of the logger used in this object. :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param quiet: If set, then no log will be emitted from this object. :param quiet_error: If set, then exception stack information will not be added to the log @@ -327,7 +319,6 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ - # overload_inject_end_flow def __init__( self, @@ -335,7 +326,7 @@ def __init__( **kwargs, ): # implementation_stub_inject_start_flow - + """Create a Flow. Flow is how Jina streamlines and scales Executors. EXAMPLE USAGE @@ -386,22 +377,22 @@ def __init__( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param no_crud_endpoints: If set, `/index`, `/search`, `/update`, `/delete` endpoints are removed from HTTP interface. - + Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -414,13 +405,13 @@ def __init__( {'/custom': 'ALL', '/search': 'ANY', '*': 'ANY'} :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] - :param prefetch: Number of requests fetched from the client before feeding into the first Executor. - + :param prefetch: Number of requests fetched from the client before feeding into the first Executor. + Used to control the speed of data input into a Flow. 0 disables prefetch (1000 requests is the default) :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -444,7 +435,7 @@ def __init__( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -452,22 +443,22 @@ def __init__( :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server - + More details can be found in Uvicorn docs: https://www.uvicorn.org/settings/ :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. :param env: The map of environment variables that are available inside runtime :param inspect: The strategy on those inspect deployments in the flow. - + If `REMOVE` is given then all inspect deployments are removed when building the flow. :param log_config: The YAML config of the logger used in this object. :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param quiet: If set, then no log will be emitted from this object. :param quiet_error: If set, then exception stack information will not be added to the log @@ -479,7 +470,7 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ - # implementation_stub_inject_end_flow + # implementation_stub_inject_end_flow super().__init__() self._version = '1' #: YAML version number, this will be later overridden if YAML config says the other way self._deployment_nodes = OrderedDict() # type: Dict[str, Deployment] @@ -785,58 +776,56 @@ def needs_all(self, name: str = 'joiner', *args, **kwargs) -> 'Flow': # overload_inject_start_deployment @overload def add( - self, - *, - compression: Optional[str] = None, - connection_list: Optional[str] = None, - disable_auto_volume: Optional[bool] = False, - disable_reduce: Optional[bool] = False, - docker_kwargs: Optional[dict] = None, - entrypoint: Optional[str] = None, - env: Optional[dict] = None, - exit_on_exceptions: Optional[List[str]] = [], - external: Optional[bool] = False, - floating: Optional[bool] = False, - force_update: Optional[bool] = False, - gpus: Optional[str] = None, - grpc_server_options: Optional[dict] = None, - host: Optional[str] = '0.0.0.0', - host_in: Optional[str] = '0.0.0.0', - install_requirements: Optional[bool] = False, - log_config: Optional[str] = None, - monitoring: Optional[bool] = False, - name: Optional[str] = None, - native: Optional[bool] = False, - output_array_type: Optional[str] = None, - polling: Optional[str] = 'ANY', - port: Optional[int] = None, - port_monitoring: Optional[str] = None, - py_modules: Optional[List[str]] = None, - quiet: Optional[bool] = False, - quiet_error: Optional[bool] = False, - quiet_remote_logs: Optional[bool] = False, - replicas: Optional[int] = 1, - retries: Optional[int] = -1, - runtime_cls: Optional[str] = 'WorkerRuntime', - shards: Optional[int] = 1, - timeout_ctrl: Optional[int] = 60, - timeout_ready: Optional[int] = 600000, - timeout_send: Optional[int] = None, - tls: Optional[bool] = False, - upload_files: Optional[List[str]] = None, - uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', - uses_after: Optional[Union[str, Type['BaseExecutor'], dict]] = None, - uses_after_address: Optional[str] = None, - uses_before: Optional[Union[str, Type['BaseExecutor'], dict]] = None, - uses_before_address: Optional[str] = None, - uses_metas: Optional[dict] = None, - uses_requests: Optional[dict] = None, - uses_with: Optional[dict] = None, - volumes: Optional[List[str]] = None, - when: Optional[dict] = None, - workspace: Optional[str] = None, - **kwargs, - ) -> Union['Flow', 'AsyncFlow']: + self,*, + compression: Optional[str] = None, + connection_list: Optional[str] = None, + disable_auto_volume: Optional[bool] = False, + disable_reduce: Optional[bool] = False, + docker_kwargs: Optional[dict] = None, + entrypoint: Optional[str] = None, + env: Optional[dict] = None, + exit_on_exceptions: Optional[List[str]] = [], + external: Optional[bool] = False, + floating: Optional[bool] = False, + force_update: Optional[bool] = False, + gpus: Optional[str] = None, + grpc_server_options: Optional[dict] = None, + host: Optional[str] = '0.0.0.0', + host_in: Optional[str] = '0.0.0.0', + install_requirements: Optional[bool] = False, + log_config: Optional[str] = None, + monitoring: Optional[bool] = False, + name: Optional[str] = None, + native: Optional[bool] = False, + output_array_type: Optional[str] = None, + polling: Optional[str] = 'ANY', + port: Optional[int] = None, + port_monitoring: Optional[str] = None, + py_modules: Optional[List[str]] = None, + quiet: Optional[bool] = False, + quiet_error: Optional[bool] = False, + quiet_remote_logs: Optional[bool] = False, + replicas: Optional[int] = 1, + retries: Optional[int] = -1, + runtime_cls: Optional[str] = 'WorkerRuntime', + shards: Optional[int] = 1, + timeout_ctrl: Optional[int] = 60, + timeout_ready: Optional[int] = 600000, + timeout_send: Optional[int] = None, + tls: Optional[bool] = False, + upload_files: Optional[List[str]] = None, + uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', + uses_after: Optional[Union[str, Type['BaseExecutor'], dict]] = None, + uses_after_address: Optional[str] = None, + uses_before: Optional[Union[str, Type['BaseExecutor'], dict]] = None, + uses_before_address: Optional[str] = None, + uses_metas: Optional[dict] = None, + uses_requests: Optional[dict] = None, + uses_with: Optional[dict] = None, + volumes: Optional[List[str]] = None, + when: Optional[dict] = None, + workspace: Optional[str] = None, + **kwargs) -> Union['Flow', 'AsyncFlow']: """Add an Executor to the current Flow object. :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. @@ -844,8 +833,8 @@ def add( :param disable_auto_volume: Do not automatically mount a volume for dockerized Executors. :param disable_reduce: Disable the built-in reduce mechanism, set this if the reduction is to be handled by the Executor connected to this Head :param docker_kwargs: Dictionary of kwargs arguments that will be passed to Docker SDK when starting the docker ' - container. - + container. + More details can be found in the Docker SDK docs: https://docker-py.readthedocs.io/en/stable/ :param entrypoint: The entrypoint command overrides the ENTRYPOINT in Docker image. when not set then the Docker image ENTRYPOINT takes effective. :param env: The map of environment variables that are available inside runtime @@ -854,8 +843,8 @@ def add( :param floating: If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. :param force_update: If set, always pull the latest Hub Executor bundle even it exists on local :param gpus: This argument allows dockerized Jina executor discover local gpu devices. - - Note, + + Note, - To access all gpus, use `--gpus all`. - To access multiple gpus, e.g. make use of 2 gpus, use `--gpus 2`. - To access specified gpus based on device id, use `--gpus device=[YOUR-GPU-DEVICE-ID]` @@ -868,18 +857,18 @@ def add( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -893,7 +882,7 @@ def add( :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -913,7 +902,7 @@ def add( workspace. This can be useful when your Deployment has more file dependencies beyond a single YAML file, e.g. Python files, data files. - + Note, - currently only flatten structure is supported, which means if you upload `[./foo/a.py, ./foo/b.pp, ./bar/c.yml]`, then they will be put under the _same_ workspace on the remote, losing all hierarchies. - by default, `--uses` YAML file is always uploaded. @@ -925,7 +914,7 @@ def add( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -936,11 +925,11 @@ def add( :param uses_metas: Dictionary of keyword arguments that will override the `metas` configuration in `uses` :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` - :param volumes: The path on the host to be mounted inside the container. - - Note, - - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. - - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. + :param volumes: The path on the host to be mounted inside the container. + + Note, + - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. + - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. - All volumes are mounted with read-write mode. :param when: The condition that the documents need to fulfill before reaching the Executor.The condition can be defined in the form of a `DocArray query condition ` :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. @@ -950,7 +939,6 @@ def add( .. # noqa: DAR101 .. # noqa: DAR003 """ - # overload_inject_end_deployment @overload def add( @@ -984,7 +972,7 @@ def add( **kwargs, ) -> Union['Flow', 'AsyncFlow']: # implementation_stub_inject_start_add - + """Add a Deployment to the current Flow object and return the new modified Flow object. The attribute of the Deployment can be later changed with :py:meth:`set` or deleted with :py:meth:`remove` @@ -993,8 +981,8 @@ def add( :param disable_auto_volume: Do not automatically mount a volume for dockerized Executors. :param disable_reduce: Disable the built-in reduce mechanism, set this if the reduction is to be handled by the Executor connected to this Head :param docker_kwargs: Dictionary of kwargs arguments that will be passed to Docker SDK when starting the docker ' - container. - + container. + More details can be found in the Docker SDK docs: https://docker-py.readthedocs.io/en/stable/ :param entrypoint: The entrypoint command overrides the ENTRYPOINT in Docker image. when not set then the Docker image ENTRYPOINT takes effective. :param env: The map of environment variables that are available inside runtime @@ -1003,8 +991,8 @@ def add( :param floating: If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. :param force_update: If set, always pull the latest Hub Executor bundle even it exists on local :param gpus: This argument allows dockerized Jina executor discover local gpu devices. - - Note, + + Note, - To access all gpus, use `--gpus all`. - To access multiple gpus, e.g. make use of 2 gpus, use `--gpus 2`. - To access specified gpus based on device id, use `--gpus device=[YOUR-GPU-DEVICE-ID]` @@ -1017,18 +1005,18 @@ def add( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -1042,7 +1030,7 @@ def add( :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -1062,7 +1050,7 @@ def add( workspace. This can be useful when your Deployment has more file dependencies beyond a single YAML file, e.g. Python files, data files. - + Note, - currently only flatten structure is supported, which means if you upload `[./foo/a.py, ./foo/b.pp, ./bar/c.yml]`, then they will be put under the _same_ workspace on the remote, losing all hierarchies. - by default, `--uses` YAML file is always uploaded. @@ -1074,7 +1062,7 @@ def add( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -1085,11 +1073,11 @@ def add( :param uses_metas: Dictionary of keyword arguments that will override the `metas` configuration in `uses` :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` - :param volumes: The path on the host to be mounted inside the container. - - Note, - - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. - - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. + :param volumes: The path on the host to be mounted inside the container. + + Note, + - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. + - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. - All volumes are mounted with read-write mode. :param when: The condition that the documents need to fulfill before reaching the Executor.The condition can be defined in the form of a `DocArray query condition ` :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. @@ -1105,7 +1093,7 @@ def add( .. # noqa: DAR101 .. # noqa: DAR003 """ - # implementation_stub_inject_end_add + # implementation_stub_inject_end_add needs = kwargs.pop('needs', None) copy_flow = kwargs.pop('copy_flow', True) diff --git a/jina/resources/extra-requirements.txt b/jina/resources/extra-requirements.txt index 2dfbf8de327a5..3394f3221ad66 100644 --- a/jina/resources/extra-requirements.txt +++ b/jina/resources/extra-requirements.txt @@ -42,6 +42,7 @@ opentelemetry-sdk>=1.12.0: perf,standard,devel opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel +opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel From a4a46218e3b7e72610a1c001bc22fdee19140234 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 20 Sep 2022 13:43:59 +0200 Subject: [PATCH 08/79] feat(instrumentation): provide opentelemety context from the grpc client to the request method --- jina/serve/instrumentation/_aio_client.py | 2 +- jina/serve/networking.py | 1 - .../request_handlers/data_request_handler.py | 7 ++++++- jina/serve/runtimes/worker/__init__.py | 13 ++++++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/jina/serve/instrumentation/_aio_client.py b/jina/serve/instrumentation/_aio_client.py index 562033f455f67..750d90f6d7bfc 100644 --- a/jina/serve/instrumentation/_aio_client.py +++ b/jina/serve/instrumentation/_aio_client.py @@ -17,7 +17,7 @@ import grpc from grpc.aio import ClientCallDetails -from opentelemetry import context, trace +from opentelemetry import context from opentelemetry.instrumentation.grpc._client import ( OpenTelemetryClientInterceptor, _carrier_setter, diff --git a/jina/serve/networking.py b/jina/serve/networking.py index 71865e545bb42..45b5ef4624df9 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -926,7 +926,6 @@ def __channel_with_tracing_interceptor( options=None, ) -> grpc.Channel: interceptor = client_tracing_interceptor() - print(f'--->type of interceptor: {type(interceptor)}') if credentials: return intercept_channel( grpc.secure_channel(address, credentials, options=options), diff --git a/jina/serve/runtimes/request_handlers/data_request_handler.py b/jina/serve/runtimes/request_handlers/data_request_handler.py index 3501c3704fcb5..fbc248f045391 100644 --- a/jina/serve/runtimes/request_handlers/data_request_handler.py +++ b/jina/serve/runtimes/request_handlers/data_request_handler.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple from docarray import DocumentArray +from opentelemetry.context.context import Context from jina import __default_endpoint__ from jina.excepts import BadConfigSource @@ -116,10 +117,13 @@ def _parse_params(parameters: Dict, executor_name: str): return parsed_params - async def handle(self, requests: List['DataRequest']) -> DataRequest: + async def handle( + self, requests: List['DataRequest'], otel_context: Context = None + ) -> DataRequest: """Initialize private parameters and execute private loading functions. :param requests: The messages to handle containing a DataRequest + :param otel_context: OpenTelemetry Context from the originating request. :returns: the processed message """ # skip executor if endpoints mismatch @@ -157,6 +161,7 @@ async def handle(self, requests: List['DataRequest']) -> DataRequest: requests, field='docs', ), + otel_context=otel_context, ) # assigning result back to request if return_data is not None: diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index 644eee19245ea..565f75d5ae0d9 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -6,6 +6,7 @@ import grpc from grpc_health.v1 import health, health_pb2, health_pb2_grpc from grpc_reflection.v1alpha import reflection +from opentelemetry.propagate import Context, extract from jina.excepts import RuntimeTerminated from jina.helper import get_full_version @@ -165,6 +166,11 @@ async def endpoint_discovery(self, empty, context) -> jina_pb2.EndpointsProto: ) return endpointsProto + @staticmethod + def _extract_tracing_context(metadata: grpc.aio.Metadata) -> Context: + context = extract(dict(metadata)) + return context + async def process_data(self, requests: List[DataRequest], context) -> DataRequest: """ Process the received requests and return the result as a new request @@ -179,7 +185,12 @@ async def process_data(self, requests: List[DataRequest], context) -> DataReques if self.logger.debug_enabled: self._log_data_request(requests[0]) - result = await self._data_request_handler.handle(requests=requests) + otel_context = WorkerRuntime._extract_tracing_context( + context.invocation_metadata() + ) + result = await self._data_request_handler.handle( + requests=requests, otel_context=otel_context + ) if self._successful_requests_metrics: self._successful_requests_metrics.inc() return result From 78efb4402a5c8597d68c92a1ea93a793b282a2bb Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 20 Sep 2022 13:49:58 +0200 Subject: [PATCH 09/79] feat(instrumentation): check for opentelemetry environment variables correctly --- jina/serve/instrumentation/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 36c254661d07a..28e378b70fe04 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -20,7 +20,7 @@ attributes={SERVICE_NAME: os.getenv('JINA_DEPLOYMENT_NAME', 'worker')} ) -if 'JINA_ENABLE_OTEL_TRACING': +if 'JINA_ENABLE_OTEL_TRACING' in os.environ: provider = TracerProvider(resource=resource) processor = BatchSpanProcessor(ConsoleSpanExporter()) provider.add_span_processor(processor) @@ -29,7 +29,7 @@ else: trace.set_tracer_provider(TRACER) -if 'JINA_ENABLE_OTEL_METRICS': +if 'JINA_ENABLE_OTEL_METRICS' in os.environ: metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) meter_provider = MeterProvider(metric_readers=[metric_reader], resource=resource) metrics.set_meter_provider(meter_provider) From 7116e9fcc816642ddcd73714a6ad1c3462c2ff24 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 20 Sep 2022 17:28:14 +0200 Subject: [PATCH 10/79] feat(instrumentation): create InstrumentationMixin for server and client implementations --- jina/clients/__init__.py | 27 +- jina/clients/base/__init__.py | 4 +- jina/orchestrate/flow/base.py | 370 ++++++++++--------- jina/parsers/client.py | 16 + jina/parsers/orchestrate/pod.py | 16 + jina/serve/executors/__init__.py | 7 + jina/serve/instrumentation/__init__.py | 93 +++-- jina/serve/instrumentation/_aio_client.py | 15 - jina/serve/instrumentation/_aio_server.py | 12 - jina/serve/networking.py | 11 +- jina/serve/runtimes/asyncio.py | 4 +- jina/serve/runtimes/gateway/grpc/__init__.py | 3 +- 12 files changed, 324 insertions(+), 254 deletions(-) diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 24799c532ac25..3ca9da1a82aa7 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -16,14 +16,25 @@ # overload_inject_start_client @overload -def Client(*, - asyncio: Optional[bool] = False, - host: Optional[str] = '0.0.0.0', - port: Optional[int] = None, - protocol: Optional[str] = 'GRPC', - proxy: Optional[bool] = False, - tls: Optional[bool] = False, - **kwargs) -> Union['AsyncWebSocketClient', 'WebSocketClient', 'AsyncGRPCClient', 'GRPCClient', 'HTTPClient', 'AsyncHTTPClient']: +def Client( + *, + asyncio: Optional[bool] = False, + host: Optional[str] = '0.0.0.0', + port: Optional[int] = None, + protocol: Optional[str] = 'GRPC', + proxy: Optional[bool] = False, + tls: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, + opentelemetry_metrics: Optional[bool] = False, + **kwargs +) -> Union[ + 'AsyncWebSocketClient', + 'WebSocketClient', + 'AsyncGRPCClient', + 'GRPCClient', + 'HTTPClient', + 'AsyncHTTPClient', +]: """Create a Client. Client is how user interact with Flow :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. diff --git a/jina/clients/base/__init__.py b/jina/clients/base/__init__.py index 90c1894e27b01..dc2bdbbca7c2a 100644 --- a/jina/clients/base/__init__.py +++ b/jina/clients/base/__init__.py @@ -17,9 +17,10 @@ InputType = Union[GeneratorSourceType, Callable[..., GeneratorSourceType]] CallbackFnType = Optional[Callable[[Response], None]] +from jina.serve.instrumentation import InstrumentationMixin -class BaseClient(ABC): +class BaseClient(InstrumentationMixin, ABC): """A base client for connecting to the Flow Gateway. :param args: the Namespace from argparse @@ -47,6 +48,7 @@ def __init__( os.unsetenv('https_proxy') self._inputs = None send_telemetry_event(event='start', obj=self) + self._setup_instrumentation() @staticmethod def check_input(inputs: Optional['InputType'] = None, **kwargs) -> None: diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index 0d9d02f982f74..bc3aa16db58f2 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -111,14 +111,16 @@ class Flow( # overload_inject_start_client_flow @overload def __init__( - self,*, - asyncio: Optional[bool] = False, - host: Optional[str] = '0.0.0.0', - port: Optional[int] = None, - protocol: Optional[str] = 'GRPC', - proxy: Optional[bool] = False, - tls: Optional[bool] = False, - **kwargs): + self, + *, + asyncio: Optional[bool] = False, + host: Optional[str] = '0.0.0.0', + port: Optional[int] = None, + protocol: Optional[str] = 'GRPC', + proxy: Optional[bool] = False, + tls: Optional[bool] = False, + **kwargs, + ): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina client` CLI. :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. @@ -132,60 +134,63 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ + # overload_inject_end_client_flow # overload_inject_start_gateway_flow @overload def __init__( - self,*, - compression: Optional[str] = None, - cors: Optional[bool] = False, - deployments_addresses: Optional[str] = '{}', - deployments_disable_reduce: Optional[str] = '[]', - description: Optional[str] = None, - env: Optional[dict] = None, - exit_on_exceptions: Optional[List[str]] = [], - expose_endpoints: Optional[str] = None, - expose_graphql_endpoint: Optional[bool] = False, - floating: Optional[bool] = False, - graph_conditions: Optional[str] = '{}', - graph_description: Optional[str] = '{}', - grpc_server_options: Optional[dict] = None, - host: Optional[str] = '0.0.0.0', - host_in: Optional[str] = '0.0.0.0', - log_config: Optional[str] = None, - monitoring: Optional[bool] = False, - name: Optional[str] = 'gateway', - native: Optional[bool] = False, - no_crud_endpoints: Optional[bool] = False, - no_debug_endpoints: Optional[bool] = False, - output_array_type: Optional[str] = None, - polling: Optional[str] = 'ANY', - port: Optional[int] = None, - port_monitoring: Optional[str] = None, - prefetch: Optional[int] = 1000, - protocol: Optional[str] = 'GRPC', - proxy: Optional[bool] = False, - py_modules: Optional[List[str]] = None, - quiet: Optional[bool] = False, - quiet_error: Optional[bool] = False, - replicas: Optional[int] = 1, - retries: Optional[int] = -1, - runtime_cls: Optional[str] = 'GRPCGatewayRuntime', - shards: Optional[int] = 1, - ssl_certfile: Optional[str] = None, - ssl_keyfile: Optional[str] = None, - timeout_ctrl: Optional[int] = 60, - timeout_ready: Optional[int] = 600000, - timeout_send: Optional[int] = None, - title: Optional[str] = None, - uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', - uses_metas: Optional[dict] = None, - uses_requests: Optional[dict] = None, - uses_with: Optional[dict] = None, - uvicorn_kwargs: Optional[dict] = None, - workspace: Optional[str] = None, - **kwargs): + self, + *, + compression: Optional[str] = None, + cors: Optional[bool] = False, + deployments_addresses: Optional[str] = '{}', + deployments_disable_reduce: Optional[str] = '[]', + description: Optional[str] = None, + env: Optional[dict] = None, + exit_on_exceptions: Optional[List[str]] = [], + expose_endpoints: Optional[str] = None, + expose_graphql_endpoint: Optional[bool] = False, + floating: Optional[bool] = False, + graph_conditions: Optional[str] = '{}', + graph_description: Optional[str] = '{}', + grpc_server_options: Optional[dict] = None, + host: Optional[str] = '0.0.0.0', + host_in: Optional[str] = '0.0.0.0', + log_config: Optional[str] = None, + monitoring: Optional[bool] = False, + name: Optional[str] = 'gateway', + native: Optional[bool] = False, + no_crud_endpoints: Optional[bool] = False, + no_debug_endpoints: Optional[bool] = False, + output_array_type: Optional[str] = None, + polling: Optional[str] = 'ANY', + port: Optional[int] = None, + port_monitoring: Optional[str] = None, + prefetch: Optional[int] = 1000, + protocol: Optional[str] = 'GRPC', + proxy: Optional[bool] = False, + py_modules: Optional[List[str]] = None, + quiet: Optional[bool] = False, + quiet_error: Optional[bool] = False, + replicas: Optional[int] = 1, + retries: Optional[int] = -1, + runtime_cls: Optional[str] = 'GRPCGatewayRuntime', + shards: Optional[int] = 1, + ssl_certfile: Optional[str] = None, + ssl_keyfile: Optional[str] = None, + timeout_ctrl: Optional[int] = 60, + timeout_ready: Optional[int] = 600000, + timeout_send: Optional[int] = None, + title: Optional[str] = None, + uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', + uses_metas: Optional[dict] = None, + uses_requests: Optional[dict] = None, + uses_with: Optional[dict] = None, + uvicorn_kwargs: Optional[dict] = None, + workspace: Optional[str] = None, + **kwargs, + ): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina gateway` CLI. :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. @@ -206,22 +211,22 @@ def __init__( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param no_crud_endpoints: If set, `/index`, `/search`, `/update`, `/delete` endpoints are removed from HTTP interface. - + Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -234,13 +239,13 @@ def __init__( {'/custom': 'ALL', '/search': 'ANY', '*': 'ANY'} :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] - :param prefetch: Number of requests fetched from the client before feeding into the first Executor. - + :param prefetch: Number of requests fetched from the client before feeding into the first Executor. + Used to control the speed of data input into a Flow. 0 disables prefetch (1000 requests is the default) :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -264,7 +269,7 @@ def __init__( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -272,7 +277,7 @@ def __init__( :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server - + More details can be found in Uvicorn docs: https://www.uvicorn.org/settings/ :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. @@ -280,35 +285,38 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ + # overload_inject_end_gateway_flow # overload_inject_start_flow @overload def __init__( - self,*, - env: Optional[dict] = None, - inspect: Optional[str] = 'COLLECT', - log_config: Optional[str] = None, - name: Optional[str] = None, - quiet: Optional[bool] = False, - quiet_error: Optional[bool] = False, - uses: Optional[str] = None, - workspace: Optional[str] = None, - **kwargs): + self, + *, + env: Optional[dict] = None, + inspect: Optional[str] = 'COLLECT', + log_config: Optional[str] = None, + name: Optional[str] = None, + quiet: Optional[bool] = False, + quiet_error: Optional[bool] = False, + uses: Optional[str] = None, + workspace: Optional[str] = None, + **kwargs, + ): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina flow` CLI. :param env: The map of environment variables that are available inside runtime :param inspect: The strategy on those inspect deployments in the flow. - + If `REMOVE` is given then all inspect deployments are removed when building the flow. :param log_config: The YAML config of the logger used in this object. :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param quiet: If set, then no log will be emitted from this object. :param quiet_error: If set, then exception stack information will not be added to the log @@ -319,6 +327,7 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ + # overload_inject_end_flow def __init__( self, @@ -326,7 +335,7 @@ def __init__( **kwargs, ): # implementation_stub_inject_start_flow - + """Create a Flow. Flow is how Jina streamlines and scales Executors. EXAMPLE USAGE @@ -377,22 +386,22 @@ def __init__( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param no_crud_endpoints: If set, `/index`, `/search`, `/update`, `/delete` endpoints are removed from HTTP interface. - + Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -405,13 +414,13 @@ def __init__( {'/custom': 'ALL', '/search': 'ANY', '*': 'ANY'} :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] - :param prefetch: Number of requests fetched from the client before feeding into the first Executor. - + :param prefetch: Number of requests fetched from the client before feeding into the first Executor. + Used to control the speed of data input into a Flow. 0 disables prefetch (1000 requests is the default) :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -435,7 +444,7 @@ def __init__( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -443,22 +452,22 @@ def __init__( :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server - + More details can be found in Uvicorn docs: https://www.uvicorn.org/settings/ :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. :param env: The map of environment variables that are available inside runtime :param inspect: The strategy on those inspect deployments in the flow. - + If `REMOVE` is given then all inspect deployments are removed when building the flow. :param log_config: The YAML config of the logger used in this object. :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param quiet: If set, then no log will be emitted from this object. :param quiet_error: If set, then exception stack information will not be added to the log @@ -470,7 +479,7 @@ def __init__( .. # noqa: DAR101 .. # noqa: DAR003 """ - # implementation_stub_inject_end_flow + # implementation_stub_inject_end_flow super().__init__() self._version = '1' #: YAML version number, this will be later overridden if YAML config says the other way self._deployment_nodes = OrderedDict() # type: Dict[str, Deployment] @@ -776,56 +785,58 @@ def needs_all(self, name: str = 'joiner', *args, **kwargs) -> 'Flow': # overload_inject_start_deployment @overload def add( - self,*, - compression: Optional[str] = None, - connection_list: Optional[str] = None, - disable_auto_volume: Optional[bool] = False, - disable_reduce: Optional[bool] = False, - docker_kwargs: Optional[dict] = None, - entrypoint: Optional[str] = None, - env: Optional[dict] = None, - exit_on_exceptions: Optional[List[str]] = [], - external: Optional[bool] = False, - floating: Optional[bool] = False, - force_update: Optional[bool] = False, - gpus: Optional[str] = None, - grpc_server_options: Optional[dict] = None, - host: Optional[str] = '0.0.0.0', - host_in: Optional[str] = '0.0.0.0', - install_requirements: Optional[bool] = False, - log_config: Optional[str] = None, - monitoring: Optional[bool] = False, - name: Optional[str] = None, - native: Optional[bool] = False, - output_array_type: Optional[str] = None, - polling: Optional[str] = 'ANY', - port: Optional[int] = None, - port_monitoring: Optional[str] = None, - py_modules: Optional[List[str]] = None, - quiet: Optional[bool] = False, - quiet_error: Optional[bool] = False, - quiet_remote_logs: Optional[bool] = False, - replicas: Optional[int] = 1, - retries: Optional[int] = -1, - runtime_cls: Optional[str] = 'WorkerRuntime', - shards: Optional[int] = 1, - timeout_ctrl: Optional[int] = 60, - timeout_ready: Optional[int] = 600000, - timeout_send: Optional[int] = None, - tls: Optional[bool] = False, - upload_files: Optional[List[str]] = None, - uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', - uses_after: Optional[Union[str, Type['BaseExecutor'], dict]] = None, - uses_after_address: Optional[str] = None, - uses_before: Optional[Union[str, Type['BaseExecutor'], dict]] = None, - uses_before_address: Optional[str] = None, - uses_metas: Optional[dict] = None, - uses_requests: Optional[dict] = None, - uses_with: Optional[dict] = None, - volumes: Optional[List[str]] = None, - when: Optional[dict] = None, - workspace: Optional[str] = None, - **kwargs) -> Union['Flow', 'AsyncFlow']: + self, + *, + compression: Optional[str] = None, + connection_list: Optional[str] = None, + disable_auto_volume: Optional[bool] = False, + disable_reduce: Optional[bool] = False, + docker_kwargs: Optional[dict] = None, + entrypoint: Optional[str] = None, + env: Optional[dict] = None, + exit_on_exceptions: Optional[List[str]] = [], + external: Optional[bool] = False, + floating: Optional[bool] = False, + force_update: Optional[bool] = False, + gpus: Optional[str] = None, + grpc_server_options: Optional[dict] = None, + host: Optional[str] = '0.0.0.0', + host_in: Optional[str] = '0.0.0.0', + install_requirements: Optional[bool] = False, + log_config: Optional[str] = None, + monitoring: Optional[bool] = False, + name: Optional[str] = None, + native: Optional[bool] = False, + output_array_type: Optional[str] = None, + polling: Optional[str] = 'ANY', + port: Optional[int] = None, + port_monitoring: Optional[str] = None, + py_modules: Optional[List[str]] = None, + quiet: Optional[bool] = False, + quiet_error: Optional[bool] = False, + quiet_remote_logs: Optional[bool] = False, + replicas: Optional[int] = 1, + retries: Optional[int] = -1, + runtime_cls: Optional[str] = 'WorkerRuntime', + shards: Optional[int] = 1, + timeout_ctrl: Optional[int] = 60, + timeout_ready: Optional[int] = 600000, + timeout_send: Optional[int] = None, + tls: Optional[bool] = False, + upload_files: Optional[List[str]] = None, + uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', + uses_after: Optional[Union[str, Type['BaseExecutor'], dict]] = None, + uses_after_address: Optional[str] = None, + uses_before: Optional[Union[str, Type['BaseExecutor'], dict]] = None, + uses_before_address: Optional[str] = None, + uses_metas: Optional[dict] = None, + uses_requests: Optional[dict] = None, + uses_with: Optional[dict] = None, + volumes: Optional[List[str]] = None, + when: Optional[dict] = None, + workspace: Optional[str] = None, + **kwargs, + ) -> Union['Flow', 'AsyncFlow']: """Add an Executor to the current Flow object. :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. @@ -833,8 +844,8 @@ def add( :param disable_auto_volume: Do not automatically mount a volume for dockerized Executors. :param disable_reduce: Disable the built-in reduce mechanism, set this if the reduction is to be handled by the Executor connected to this Head :param docker_kwargs: Dictionary of kwargs arguments that will be passed to Docker SDK when starting the docker ' - container. - + container. + More details can be found in the Docker SDK docs: https://docker-py.readthedocs.io/en/stable/ :param entrypoint: The entrypoint command overrides the ENTRYPOINT in Docker image. when not set then the Docker image ENTRYPOINT takes effective. :param env: The map of environment variables that are available inside runtime @@ -843,8 +854,8 @@ def add( :param floating: If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. :param force_update: If set, always pull the latest Hub Executor bundle even it exists on local :param gpus: This argument allows dockerized Jina executor discover local gpu devices. - - Note, + + Note, - To access all gpus, use `--gpus all`. - To access multiple gpus, e.g. make use of 2 gpus, use `--gpus 2`. - To access specified gpus based on device id, use `--gpus device=[YOUR-GPU-DEVICE-ID]` @@ -857,18 +868,18 @@ def add( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -882,7 +893,7 @@ def add( :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -902,7 +913,7 @@ def add( workspace. This can be useful when your Deployment has more file dependencies beyond a single YAML file, e.g. Python files, data files. - + Note, - currently only flatten structure is supported, which means if you upload `[./foo/a.py, ./foo/b.pp, ./bar/c.yml]`, then they will be put under the _same_ workspace on the remote, losing all hierarchies. - by default, `--uses` YAML file is always uploaded. @@ -914,7 +925,7 @@ def add( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -925,11 +936,11 @@ def add( :param uses_metas: Dictionary of keyword arguments that will override the `metas` configuration in `uses` :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` - :param volumes: The path on the host to be mounted inside the container. - - Note, - - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. - - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. + :param volumes: The path on the host to be mounted inside the container. + + Note, + - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. + - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. - All volumes are mounted with read-write mode. :param when: The condition that the documents need to fulfill before reaching the Executor.The condition can be defined in the form of a `DocArray query condition ` :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. @@ -939,6 +950,7 @@ def add( .. # noqa: DAR101 .. # noqa: DAR003 """ + # overload_inject_end_deployment @overload def add( @@ -972,7 +984,7 @@ def add( **kwargs, ) -> Union['Flow', 'AsyncFlow']: # implementation_stub_inject_start_add - + """Add a Deployment to the current Flow object and return the new modified Flow object. The attribute of the Deployment can be later changed with :py:meth:`set` or deleted with :py:meth:`remove` @@ -981,8 +993,8 @@ def add( :param disable_auto_volume: Do not automatically mount a volume for dockerized Executors. :param disable_reduce: Disable the built-in reduce mechanism, set this if the reduction is to be handled by the Executor connected to this Head :param docker_kwargs: Dictionary of kwargs arguments that will be passed to Docker SDK when starting the docker ' - container. - + container. + More details can be found in the Docker SDK docs: https://docker-py.readthedocs.io/en/stable/ :param entrypoint: The entrypoint command overrides the ENTRYPOINT in Docker image. when not set then the Docker image ENTRYPOINT takes effective. :param env: The map of environment variables that are available inside runtime @@ -991,8 +1003,8 @@ def add( :param floating: If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. :param force_update: If set, always pull the latest Hub Executor bundle even it exists on local :param gpus: This argument allows dockerized Jina executor discover local gpu devices. - - Note, + + Note, - To access all gpus, use `--gpus all`. - To access multiple gpus, e.g. make use of 2 gpus, use `--gpus 2`. - To access specified gpus based on device id, use `--gpus device=[YOUR-GPU-DEVICE-ID]` @@ -1005,18 +1017,18 @@ def add( :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. - + This will be used in the following places: - how you refer to this object in Python/YAML/CLI - visualization - log message header - ... - + When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. - - Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found + + Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found `here `. Defaults to retaining whatever type is returned by the Executor. :param polling: The polling strategy of the Deployment and its endpoints (when `shards>1`). @@ -1030,7 +1042,7 @@ def add( :param port: The port for input data to bind to, default is a random port between [49152, 65535] :param port_monitoring: The port on which the prometheus server is exposed, default is a random port between [49152, 65535] :param py_modules: The customized python modules need to be imported before loading the executor - + Note that the recommended way is to only import a single module - a simple python file, if your executor can be defined in a single file, or an ``__init__.py`` file if you have multiple files, which should be structured as a python package. For more details, please see the @@ -1050,7 +1062,7 @@ def add( workspace. This can be useful when your Deployment has more file dependencies beyond a single YAML file, e.g. Python files, data files. - + Note, - currently only flatten structure is supported, which means if you upload `[./foo/a.py, ./foo/b.pp, ./bar/c.yml]`, then they will be put under the _same_ workspace on the remote, losing all hierarchies. - by default, `--uses` YAML file is always uploaded. @@ -1062,7 +1074,7 @@ def add( * a docker image (must start with `docker://`) * the string literal of a YAML config (must start with `!` or `jtype: `) * the string literal of a JSON config - + When use it under Python, one can use the following values additionally: - a Python dict that represents the config - a text file stream has `.read()` interface @@ -1073,11 +1085,11 @@ def add( :param uses_metas: Dictionary of keyword arguments that will override the `metas` configuration in `uses` :param uses_requests: Dictionary of keyword arguments that will override the `requests` configuration in `uses` :param uses_with: Dictionary of keyword arguments that will override the `with` configuration in `uses` - :param volumes: The path on the host to be mounted inside the container. - - Note, - - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. - - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. + :param volumes: The path on the host to be mounted inside the container. + + Note, + - If separated by `:`, then the first part will be considered as the local host path and the second part is the path in the container system. + - If no split provided, then the basename of that directory will be mounted into container's root path, e.g. `--volumes="/user/test/my-workspace"` will be mounted into `/my-workspace` inside the container. - All volumes are mounted with read-write mode. :param when: The condition that the documents need to fulfill before reaching the Executor.The condition can be defined in the form of a `DocArray query condition ` :param workspace: The working directory for any IO operations in this object. If not set, then derive from its parent `workspace`. @@ -1093,7 +1105,7 @@ def add( .. # noqa: DAR101 .. # noqa: DAR003 """ - # implementation_stub_inject_end_add + # implementation_stub_inject_end_add needs = kwargs.pop('needs', None) copy_flow = kwargs.pop('copy_flow', True) diff --git a/jina/parsers/client.py b/jina/parsers/client.py index 76634ab10516e..79f59c8e65788 100644 --- a/jina/parsers/client.py +++ b/jina/parsers/client.py @@ -30,3 +30,19 @@ def mixin_client_features_parser(parser): default=False, help='If set, then the input and output of this Client work in an asynchronous manner. ', ) + + parser.add_argument( + '--opentelemetry-tracing', + action='store_true', + default=False, + help='If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. ' + 'Otherwise a no-op implementation will be provided.', + ) + + parser.add_argument( + '--opentelemetry-metrics', + action='store_true', + default=False, + help='If set, real implementation of the metrics will be available for default monitoring and custom measurements. ' + 'Otherwise a no-op implementation will be provided.', + ) diff --git a/jina/parsers/orchestrate/pod.py b/jina/parsers/orchestrate/pod.py index 612a15e32fa2a..ce14e43048fe1 100644 --- a/jina/parsers/orchestrate/pod.py +++ b/jina/parsers/orchestrate/pod.py @@ -119,3 +119,19 @@ def mixin_pod_parser(parser): help='If set, the current Pod/Deployment can not be further chained, ' 'and the next `.add()` will chain after the last Pod/Deployment not this current one.', ) + + gp.add_argument( + '--opentelemetry-tracing', + action='store_true', + default=False, + help='If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. ' + 'Otherwise a no-op implementation will be provided.', + ) + + gp.add_argument( + '--opentelemetry-metrics', + action='store_true', + default=False, + help='If set, real implementation of the metrics will be available for default monitoring and custom measurements. ' + 'Otherwise a no-op implementation will be provided.', + ) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 62fc8d5c750fb..819bac32db46e 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -7,6 +7,8 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union +from opentelemetry import metrics, trace + from jina import __args_executor_init__, __cache_path__, __default_endpoint__ from jina.enums import BetterEnum from jina.helper import ( @@ -140,6 +142,7 @@ def __init__( self._add_requests(requests) self._add_runtime_args(runtime_args) self._init_monitoring() + self._init_instrumentation() self._init_workspace = workspace self.logger = JinaLogger(self.__class__.__name__) if __dry_run_endpoint__ not in self.requests: @@ -186,6 +189,10 @@ def _init_monitoring(self): self._summary_method = None self._metrics_buffer = None + def _init_instrumentation(self): + self.tracer = trace.get_tracer(self.runtime_args.name) + self.meter = metrics.get_meter(self.runtime_args.name) + def _add_requests(self, _requests: Optional[Dict]): if not hasattr(self, 'requests'): self.requests = {} diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 28e378b70fe04..8a1c2747762fa 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,6 +1,3 @@ -# These are the necessary import declarations -import os - from opentelemetry import metrics, trace from opentelemetry.instrumentation.grpc import ( client_interceptor as grpc_client_interceptor, @@ -14,33 +11,67 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter -TRACER = trace.NoOpTracer -METER = metrics.NoOpMeter -resource = Resource( - attributes={SERVICE_NAME: os.getenv('JINA_DEPLOYMENT_NAME', 'worker')} +from jina.serve.instrumentation._aio_client import ( + StreamStreamAioClientInterceptor, + StreamUnaryAioClientInterceptor, + UnaryStreamAioClientInterceptor, + UnaryUnaryAioClientInterceptor, ) -if 'JINA_ENABLE_OTEL_TRACING' in os.environ: - provider = TracerProvider(resource=resource) - processor = BatchSpanProcessor(ConsoleSpanExporter()) - provider.add_span_processor(processor) - trace.set_tracer_provider(provider) - TRACER = trace.get_tracer(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) -else: - trace.set_tracer_provider(TRACER) - -if 'JINA_ENABLE_OTEL_METRICS' in os.environ: - metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) - meter_provider = MeterProvider(metric_readers=[metric_reader], resource=resource) - metrics.set_meter_provider(meter_provider) - # Sets the global meter provider - METER = metrics.get_meter(os.getenv('JINA_DEPLOYMENT_NAME', 'worker')) -else: - metrics.set_meter_provider(METER) - - -def client_tracing_interceptor(): - ''' - :returns: a gRPC client interceptor with the global tracing provider. - ''' - return grpc_client_interceptor(trace.get_tracer_provider()) + +class InstrumentationMixin: + '''Instrumentation mixin for OpenTelemetery Tracing and Metrics handling''' + + def __init__(self) -> None: + self.tracer = trace.NoOpTracer() + self.meter = metrics.NoOpMeter(name='no-op') + + def _setup_instrumentation(self) -> None: + name = self.__class__.__name__ + if hasattr(self, 'name') and self.name: + name = self.name + resource = Resource(attributes={SERVICE_NAME: name}) + + if self.args.opentelemetry_tracing: + provider = TracerProvider(resource=resource) + processor = BatchSpanProcessor(ConsoleSpanExporter()) + provider.add_span_processor(processor) + trace.set_tracer_provider(provider) + self.tracer = trace.get_tracer(name) + + if self.args.opentelemetry_metrics: + metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) + meter_provider = MeterProvider( + metric_readers=[metric_reader], resource=resource + ) + metrics.set_meter_provider(meter_provider) + self.meter = metrics.get_meter(name) + + def aio_tracing_server_interceptor(self): + '''Create a gRPC aio server interceptor. + :returns: A service-side aio interceptor object. + ''' + from . import _aio_server + + return _aio_server.OpenTelemetryAioServerInterceptor(self.tracer) + + @staticmethod + def aio_tracing_client_interceptors(): + '''Create a gRPC client aio channel interceptor. + :returns: An invocation-side list of aio interceptor objects. + ''' + tracer = trace.get_tracer(__name__) + + return [ + UnaryUnaryAioClientInterceptor(tracer), + UnaryStreamAioClientInterceptor(tracer), + StreamUnaryAioClientInterceptor(tracer), + StreamStreamAioClientInterceptor(tracer), + ] + + @staticmethod + def tracing_client_interceptor(): + ''' + :returns: a gRPC client interceptor with the global tracing provider. + ''' + return grpc_client_interceptor(trace.get_tracer_provider()) diff --git a/jina/serve/instrumentation/_aio_client.py b/jina/serve/instrumentation/_aio_client.py index 750d90f6d7bfc..a8d00568e21cf 100644 --- a/jina/serve/instrumentation/_aio_client.py +++ b/jina/serve/instrumentation/_aio_client.py @@ -27,8 +27,6 @@ from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace.status import Status, StatusCode -from jina.serve.instrumentation import TRACER - def _unary_done_callback(span, code, details): def callback(call): @@ -319,16 +317,3 @@ async def intercept_stream_stream( resp = await continuation(new_details, request_iterator) return self._wrap_stream_response(span, resp) - - -def aio_client_interceptors(): - '''Create a gRPC client aio channel interceptor. - :returns: An invocation-side list of aio interceptor objects. - ''' - - return [ - UnaryUnaryAioClientInterceptor(TRACER), - UnaryStreamAioClientInterceptor(TRACER), - StreamUnaryAioClientInterceptor(TRACER), - StreamStreamAioClientInterceptor(TRACER), - ] diff --git a/jina/serve/instrumentation/_aio_server.py b/jina/serve/instrumentation/_aio_server.py index 673b3633dbb37..46aaf13ace29f 100644 --- a/jina/serve/instrumentation/_aio_server.py +++ b/jina/serve/instrumentation/_aio_server.py @@ -13,15 +13,12 @@ # limitations under the License. import grpc.aio -from opentelemetry import trace from opentelemetry.instrumentation.grpc._server import ( OpenTelemetryServerInterceptor, _OpenTelemetryServicerContext, _wrap_rpc_behavior, ) -from jina.serve.instrumentation import TRACER - class OpenTelemetryAioServerInterceptor( grpc.aio.ServerInterceptor, OpenTelemetryServerInterceptor @@ -109,12 +106,3 @@ async def _stream_interceptor(request_or_iterator, context): raise error return _stream_interceptor - - -def aio_server_interceptor(): - '''Create a gRPC aio server interceptor. - :returns: A service-side aio interceptor object. - ''' - from . import _aio_server - - return _aio_server.OpenTelemetryAioServerInterceptor(TRACER) diff --git a/jina/serve/networking.py b/jina/serve/networking.py index 45b5ef4624df9..b799fdb5a8ec4 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -19,8 +19,7 @@ from jina.importer import ImportExtensions from jina.logging.logger import JinaLogger from jina.proto import jina_pb2, jina_pb2_grpc -from jina.serve.instrumentation import client_tracing_interceptor -from jina.serve.instrumentation._aio_client import aio_client_interceptors +from jina.serve.instrumentation import InstrumentationMixin from jina.types.request import Request from jina.types.request.data import DataRequest @@ -913,10 +912,12 @@ def __aio_channel_with_tracing_interceptor( address, credentials, options=options, - interceptors=aio_client_interceptors(), + interceptors=InstrumentationMixin.aio_tracing_client_interceptors(), ) return grpc.aio.insecure_channel( - address, options=options, interceptors=aio_client_interceptors() + address, + options=options, + interceptors=InstrumentationMixin.aio_tracing_client_interceptors(), ) @staticmethod @@ -925,7 +926,7 @@ def __channel_with_tracing_interceptor( credentials=None, options=None, ) -> grpc.Channel: - interceptor = client_tracing_interceptor() + interceptor = InstrumentationMixin.tracing_client_interceptor() if credentials: return intercept_channel( grpc.secure_channel(address, credentials, options=options), diff --git a/jina/serve/runtimes/asyncio.py b/jina/serve/runtimes/asyncio.py index 6681f179b7517..8f82a2ea088ec 100644 --- a/jina/serve/runtimes/asyncio.py +++ b/jina/serve/runtimes/asyncio.py @@ -11,6 +11,7 @@ from jina import __windows__ from jina.helper import send_telemetry_event from jina.importer import ImportExtensions +from jina.serve.instrumentation import InstrumentationMixin from jina.serve.networking import GrpcConnectionPool from jina.serve.runtimes.base import BaseRuntime from jina.serve.runtimes.monitoring import MonitoringMixin @@ -21,7 +22,7 @@ import threading -class AsyncNewLoopRuntime(BaseRuntime, MonitoringMixin, ABC): +class AsyncNewLoopRuntime(BaseRuntime, MonitoringMixin, InstrumentationMixin, ABC): """ The async runtime to start a new event loop. """ @@ -65,6 +66,7 @@ def __init__( ) self._setup_monitoring() + self._setup_instrumentation() send_telemetry_event(event='start', obj=self) self._loop.run_until_complete(self.async_setup()) diff --git a/jina/serve/runtimes/gateway/grpc/__init__.py b/jina/serve/runtimes/gateway/grpc/__init__.py index 6032b5a4a429f..85d6f9ea21a79 100644 --- a/jina/serve/runtimes/gateway/grpc/__init__.py +++ b/jina/serve/runtimes/gateway/grpc/__init__.py @@ -10,7 +10,6 @@ from jina.helper import get_full_version, is_port_free from jina.proto import jina_pb2, jina_pb2_grpc from jina.serve.bff import GatewayBFF -from jina.serve.instrumentation._aio_server import aio_server_interceptor from jina.serve.runtimes.gateway import GatewayRuntime from jina.serve.runtimes.helper import _get_grpc_server_options from jina.types.request.status import StatusMessage @@ -48,7 +47,7 @@ async def async_setup(self): self.server = grpc.aio.server( options=_get_grpc_server_options(self.args.grpc_server_options), - interceptors=[aio_server_interceptor()], + interceptors=[self.aio_tracing_server_interceptor()], ) await self._async_setup_server() From 92d367939684c48cbd2a230b1c60e16d2ce7222d Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 21 Sep 2022 09:49:32 +0200 Subject: [PATCH 11/79] chore(instrumentation): use absolute module import --- jina/serve/instrumentation/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 8a1c2747762fa..1579e1e4ac66a 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -51,9 +51,11 @@ def aio_tracing_server_interceptor(self): '''Create a gRPC aio server interceptor. :returns: A service-side aio interceptor object. ''' - from . import _aio_server + from jina.serve.instrumentation._aio_server import ( + OpenTelemetryAioServerInterceptor, + ) - return _aio_server.OpenTelemetryAioServerInterceptor(self.tracer) + return OpenTelemetryAioServerInterceptor(self.tracer) @staticmethod def aio_tracing_client_interceptors(): From eb0ccd3afd0497737855952002e2ccd6eeee1a7b Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 21 Sep 2022 14:51:14 +0200 Subject: [PATCH 12/79] feat(instrumentation): trace http and websocket server and clients --- extra-requirements.txt | 2 + jina/clients/base/helper.py | 20 ++++- jina/serve/runtimes/gateway/http/app.py | 83 +++++++++++--------- jina/serve/runtimes/gateway/websocket/app.py | 59 ++++++++------ 4 files changed, 98 insertions(+), 66 deletions(-) diff --git a/extra-requirements.txt b/extra-requirements.txt index 3394f3221ad66..191e902c6d84c 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -43,6 +43,8 @@ opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel +opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel +opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel diff --git a/jina/clients/base/helper.py b/jina/clients/base/helper.py index 2623e2e51955c..5163f33e83af8 100644 --- a/jina/clients/base/helper.py +++ b/jina/clients/base/helper.py @@ -1,8 +1,12 @@ +import argparse import asyncio from abc import ABC, abstractmethod -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from aiohttp import WSMsgType +from opentelemetry import trace +from opentelemetry.instrumentation.aiohttp_client import create_trace_config +from opentelemetry.propagate import inject from jina.enums import WebsocketSubProtocols from jina.importer import ImportExtensions @@ -17,7 +21,12 @@ class AioHttpClientlet(ABC): """aiohttp session manager""" - def __init__(self, url: str, logger: 'JinaLogger', **kwargs) -> None: + def __init__( + self, + url: str, + logger: 'JinaLogger', + **kwargs, + ) -> None: """HTTP Client to be used with the streamer :param url: url to send http/websocket request to @@ -28,6 +37,9 @@ def __init__(self, url: str, logger: 'JinaLogger', **kwargs) -> None: self.logger = logger self.msg_recv = 0 self.msg_sent = 0 + self._trace_config = create_trace_config( + tracer_provider=trace.get_tracer_provider(), + ) self.session = None self._session_kwargs = {} if kwargs.get('headers', None): @@ -75,7 +87,9 @@ async def start(self): with ImportExtensions(required=True): import aiohttp - self.session = aiohttp.ClientSession(**self._session_kwargs) + self.session = aiohttp.ClientSession( + **self._session_kwargs, trace_configs=[self._trace_config] + ) await self.session.__aenter__() return self diff --git a/jina/serve/runtimes/gateway/http/app.py b/jina/serve/runtimes/gateway/http/app.py index 558547a4662b8..db24dfdf20d2e 100644 --- a/jina/serve/runtimes/gateway/http/app.py +++ b/jina/serve/runtimes/gateway/http/app.py @@ -2,6 +2,8 @@ import json from typing import TYPE_CHECKING, Dict, List, Optional +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + from jina import __version__ from jina.clients.request import request_generator from jina.enums import DataInputType @@ -15,10 +17,10 @@ def get_fastapi_app( - args: 'argparse.Namespace', - logger: 'JinaLogger', - timeout_send: Optional[float] = None, - metrics_registry: Optional['CollectorRegistry'] = None, + args: 'argparse.Namespace', + logger: 'JinaLogger', + timeout_send: Optional[float] = None, + metrics_registry: Optional['CollectorRegistry'] = None, ): """ Get the app from FastAPI as the REST interface. @@ -42,11 +44,14 @@ def get_fastapi_app( app = FastAPI( title=args.title or 'My Jina Service', description=args.description - or 'This is my awesome service. You can set `title` and `description` in your `Flow` or `Gateway` ' - 'to customize the title and description.', + or 'This is my awesome service. You can set `title` and `description` in your `Flow` or `Gateway` ' + 'to customize the title and description.', version=__version__, ) + if args.opentelemetry_tracing: + FastAPIInstrumentor.instrument_app(app) + if args.cors: app.add_middleware( CORSMiddleware, @@ -57,26 +62,28 @@ def get_fastapi_app( ) logger.warning('CORS is enabled. This service is accessible from any website!') - from jina.serve.bff import GatewayBFF - import json + from jina.serve.bff import GatewayBFF + graph_description = json.loads(args.graph_description) graph_conditions = json.loads(args.graph_conditions) deployments_addresses = json.loads(args.deployments_addresses) deployments_disable_reduce = json.loads(args.deployments_disable_reduce) - gateway_bff = GatewayBFF(graph_representation=graph_description, - executor_addresses=deployments_addresses, - graph_conditions=graph_conditions, - deployments_disable_reduce=deployments_disable_reduce, - timeout_send=timeout_send, - retries=args.retries, - compression=args.compression, - runtime_name=args.name, - prefetch=args.prefetch, - logger=logger, - metrics_registry=metrics_registry) + gateway_bff = GatewayBFF( + graph_representation=graph_description, + executor_addresses=deployments_addresses, + graph_conditions=graph_conditions, + deployments_disable_reduce=deployments_disable_reduce, + timeout_send=timeout_send, + retries=args.retries, + compression=args.compression, + runtime_name=args.name, + prefetch=args.prefetch, + logger=logger, + metrics_registry=metrics_registry, + ) @app.on_event('shutdown') async def _shutdown(): @@ -88,7 +95,7 @@ async def _shutdown(): { 'name': 'Debug', 'description': 'Debugging interface. In production, you should hide them by setting ' - '`--no-debug-endpoints` in `Flow`/`Gateway`.', + '`--no-debug-endpoints` in `Flow`/`Gateway`.', } ) @@ -108,6 +115,7 @@ async def _gateway_health(): return {} from docarray import DocumentArray + from jina.proto import jina_pb2 from jina.serve.executors import __dry_run_endpoint__ from jina.serve.runtimes.gateway.http.models import ( @@ -119,7 +127,7 @@ async def _gateway_health(): @app.get( path='/dry_run', summary='Get the readiness of Jina Flow service, sends an empty DocumentArray to the complete Flow to ' - 'validate connectivity', + 'validate connectivity', response_model=PROTO_TO_PYDANTIC_MODELS.StatusProto, ) async def _flow_health(): @@ -176,7 +184,7 @@ async def _status(): # do not add response_model here, this debug endpoint should not restricts the response model ) async def post( - body: JinaEndpointRequestModel, response: Response + body: JinaEndpointRequestModel, response: Response ): # 'response' is a FastAPI response, not a Jina response """ Post a data request to some endpoint. @@ -283,7 +291,7 @@ async def foo(body: JinaRequestModel): { 'name': 'CRUD', 'description': 'CRUD interface. If your service does not implement those interfaces, you can should ' - 'hide them by setting `--no-crud-endpoints` in `Flow`/`Gateway`.', + 'hide them by setting `--no-crud-endpoints` in `Flow`/`Gateway`.', } ) crud = { @@ -312,6 +320,7 @@ async def foo(body: JinaRequestModel): from dataclasses import asdict import strawberry + from docarray import DocumentArray from docarray.document.strawberry_type import ( JSONScalar, StrawberryDocument, @@ -319,10 +328,8 @@ async def foo(body: JinaRequestModel): ) from strawberry.fastapi import GraphQLRouter - from docarray import DocumentArray - async def get_docs_from_endpoint( - data, target_executor, parameters, exec_endpoint + data, target_executor, parameters, exec_endpoint ): req_generator_input = { 'data': [asdict(d) for d in data], @@ -333,8 +340,8 @@ async def get_docs_from_endpoint( } if ( - req_generator_input['data'] is not None - and 'docs' in req_generator_input['data'] + req_generator_input['data'] is not None + and 'docs' in req_generator_input['data'] ): req_generator_input['data'] = req_generator_input['data']['docs'] try: @@ -352,11 +359,11 @@ async def get_docs_from_endpoint( class Mutation: @strawberry.mutation async def docs( - self, - data: Optional[List[StrawberryDocumentInput]] = None, - target_executor: Optional[str] = None, - parameters: Optional[JSONScalar] = None, - exec_endpoint: str = '/search', + self, + data: Optional[List[StrawberryDocumentInput]] = None, + target_executor: Optional[str] = None, + parameters: Optional[JSONScalar] = None, + exec_endpoint: str = '/search', ) -> List[StrawberryDocument]: return await get_docs_from_endpoint( data, target_executor, parameters, exec_endpoint @@ -366,11 +373,11 @@ async def docs( class Query: @strawberry.field async def docs( - self, - data: Optional[List[StrawberryDocumentInput]] = None, - target_executor: Optional[str] = None, - parameters: Optional[JSONScalar] = None, - exec_endpoint: str = '/search', + self, + data: Optional[List[StrawberryDocumentInput]] = None, + target_executor: Optional[str] = None, + parameters: Optional[JSONScalar] = None, + exec_endpoint: str = '/search', ) -> List[StrawberryDocument]: return await get_docs_from_endpoint( data, target_executor, parameters, exec_endpoint diff --git a/jina/serve/runtimes/gateway/websocket/app.py b/jina/serve/runtimes/gateway/websocket/app.py index dade64791c6e8..bf045340bf0ba 100644 --- a/jina/serve/runtimes/gateway/websocket/app.py +++ b/jina/serve/runtimes/gateway/websocket/app.py @@ -1,6 +1,8 @@ import argparse from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Union +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + from jina.clients.request import request_generator from jina.enums import DataInputType, WebsocketSubProtocols from jina.excepts import InternalNetworkError @@ -22,10 +24,10 @@ def _fits_ws_close_msg(msg: str): def get_fastapi_app( - args: 'argparse.Namespace', - logger: 'JinaLogger', - timeout_send: Optional[float] = None, - metrics_registry: Optional['CollectorRegistry'] = None, + args: 'argparse.Namespace', + logger: 'JinaLogger', + timeout_send: Optional[float] = None, + metrics_registry: Optional['CollectorRegistry'] = None, ): """ Get the app from FastAPI as the Websocket interface. @@ -101,7 +103,7 @@ async def iter(self, websocket: WebSocket) -> AsyncIterator[Any]: pass async def send( - self, websocket: WebSocket, data: Union[DataRequest, StatusMessage] + self, websocket: WebSocket, data: Union[DataRequest, StatusMessage] ) -> None: subprotocol = self.protocol_dict[self.get_client(websocket)] if subprotocol == WebsocketSubProtocols.JSON: @@ -113,25 +115,31 @@ async def send( app = FastAPI() - from jina.serve.bff import GatewayBFF + if args.opentelemetry_tracing: + FastAPIInstrumentor.instrument_app(app) + import json + from jina.serve.bff import GatewayBFF + graph_description = json.loads(args.graph_description) graph_conditions = json.loads(args.graph_conditions) deployments_addresses = json.loads(args.deployments_addresses) deployments_disable_reduce = json.loads(args.deployments_disable_reduce) - gateway_bff = GatewayBFF(graph_representation=graph_description, - executor_addresses=deployments_addresses, - graph_conditions=graph_conditions, - deployments_disable_reduce=deployments_disable_reduce, - timeout_send=timeout_send, - retries=args.retries, - compression=args.compression, - runtime_name=args.name, - prefetch=args.prefetch, - logger=logger, - metrics_registry=metrics_registry) + gateway_bff = GatewayBFF( + graph_representation=graph_description, + executor_addresses=deployments_addresses, + graph_conditions=graph_conditions, + deployments_disable_reduce=deployments_disable_reduce, + timeout_send=timeout_send, + retries=args.retries, + compression=args.compression, + runtime_name=args.name, + prefetch=args.prefetch, + logger=logger, + metrics_registry=metrics_registry, + ) @app.get( path='/', @@ -170,7 +178,7 @@ async def _shutdown(): @app.websocket('/') async def websocket_endpoint( - websocket: WebSocket, response: Response + websocket: WebSocket, response: Response ): # 'response' is a FastAPI response, not a Jina response await manager.connect(websocket) @@ -233,6 +241,7 @@ async def _get_singleton_result(request_iterator) -> Dict: return request_dict from docarray import DocumentArray + from jina.proto import jina_pb2 from jina.serve.executors import __dry_run_endpoint__ from jina.serve.runtimes.gateway.http.models import PROTO_TO_PYDANTIC_MODELS @@ -240,7 +249,7 @@ async def _get_singleton_result(request_iterator) -> Dict: @app.get( path='/dry_run', summary='Get the readiness of Jina Flow service, sends an empty DocumentArray to the complete Flow to ' - 'validate connectivity', + 'validate connectivity', response_model=PROTO_TO_PYDANTIC_MODELS.StatusProto, ) async def _dry_run_http(): @@ -270,7 +279,7 @@ async def _dry_run_http(): @app.websocket('/dry_run') async def websocket_endpoint( - websocket: WebSocket, response: Response + websocket: WebSocket, response: Response ): # 'response' is a FastAPI response, not a Jina response from jina.proto import jina_pb2 from jina.serve.executors import __dry_run_endpoint__ @@ -280,11 +289,11 @@ async def websocket_endpoint( da = DocumentArray() try: async for _ in gateway_bff.stream( - request_iterator=request_generator( - exec_endpoint=__dry_run_endpoint__, - data=da, - data_type=DataInputType.DOCUMENT, - ) + request_iterator=request_generator( + exec_endpoint=__dry_run_endpoint__, + data=da, + data_type=DataInputType.DOCUMENT, + ) ): pass status_message = StatusMessage() From 38cae6167b7faf6b34aedb379f267d3ad0a6687d Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 21 Sep 2022 15:52:03 +0200 Subject: [PATCH 13/79] chore(instrumentation): update/add new opentelemetry arguments --- jina/clients/__init__.py | 6 ++++-- jina/orchestrate/flow/base.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 3ca9da1a82aa7..58566e3e7f51b 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -20,12 +20,12 @@ def Client( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', + opentelemetry_tracing: Optional[bool] = False, + opentelemetry_metrics: Optional[bool] = False, port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, tls: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, - opentelemetry_metrics: Optional[bool] = False, **kwargs ) -> Union[ 'AsyncWebSocketClient', @@ -39,6 +39,8 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param opentelemetry_tracing: 'If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided.' + :param opentelemetry_metrics: 'If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided.' :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index bc3aa16db58f2..202b9598dcb07 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -163,6 +163,8 @@ def __init__( native: Optional[bool] = False, no_crud_endpoints: Optional[bool] = False, no_debug_endpoints: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, + opentelemetry_metrics: Optional[bool] = False, output_array_type: Optional[str] = None, polling: Optional[str] = 'ANY', port: Optional[int] = None, @@ -224,6 +226,8 @@ def __init__( Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. + :param opentelemetry_tracing: 'If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided.' + :param opentelemetry_metrics: 'If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided.' :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -807,6 +811,8 @@ def add( monitoring: Optional[bool] = False, name: Optional[str] = None, native: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, + opentelemetry_metrics: Optional[bool] = False, output_array_type: Optional[str] = None, polling: Optional[str] = 'ANY', port: Optional[int] = None, @@ -877,6 +883,8 @@ def add( When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. + :param opentelemetry_tracing: 'If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided.' + :param opentelemetry_metrics: 'If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided.' :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found From 45d1794bba1cb3f648c41ee2bcca094a033d9cfd Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 21 Sep 2022 20:18:36 +0200 Subject: [PATCH 14/79] feat(instrumentation): globally disable tracing health check requests --- jina/serve/networking.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/jina/serve/networking.py b/jina/serve/networking.py index b799fdb5a8ec4..ccd0013707e0b 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -906,18 +906,19 @@ def __aio_channel_with_tracing_interceptor( address, credentials=None, options=None, + interceptors=None, ) -> grpc.aio.Channel: if credentials: return grpc.aio.secure_channel( address, credentials, options=options, - interceptors=InstrumentationMixin.aio_tracing_client_interceptors(), + interceptors=interceptors, ) return grpc.aio.insecure_channel( address, options=options, - interceptors=InstrumentationMixin.aio_tracing_client_interceptors(), + interceptors=interceptors, ) @staticmethod @@ -925,17 +926,20 @@ def __channel_with_tracing_interceptor( address, credentials=None, options=None, + interceptor=None, ) -> grpc.Channel: - interceptor = InstrumentationMixin.tracing_client_interceptor() if credentials: + channel = grpc.secure_channel(address, credentials, options=options) + else: + channel = grpc.insecure_channel(address, options=options) + + if interceptor: return intercept_channel( - grpc.secure_channel(address, credentials, options=options), + channel, interceptor, ) - return intercept_channel( - grpc.insecure_channel(address, options=options), - interceptor, - ) + else: + return channel @staticmethod def get_grpc_channel( @@ -944,6 +948,7 @@ def get_grpc_channel( asyncio: bool = False, tls: bool = False, root_certificates: Optional[str] = None, + enable_trace: Optional[bool] = True, ) -> grpc.Channel: """ Creates a grpc channel to the given address @@ -953,6 +958,7 @@ def get_grpc_channel( :param asyncio: If True, use the asyncio implementation of the grpc channel :param tls: If True, use tls encryption for the grpc channel :param root_certificates: The path to the root certificates for tls, only used if tls is True + :param enable_trace: If True the tracing interceptors are added to the channel otherwise the channel will not be provided with any tracing interceptors. :return: A grpc channel or an asyncio channel """ @@ -967,12 +973,20 @@ def get_grpc_channel( ) if asyncio: + interceptors = ( + InstrumentationMixin.aio_tracing_client_interceptors() + if enable_trace + else None + ) return GrpcConnectionPool.__aio_channel_with_tracing_interceptor( - address, credentials, options + address, credentials, options, interceptors ) + interceptor = ( + InstrumentationMixin.tracing_client_interceptor() if enable_trace else None + ) return GrpcConnectionPool.__channel_with_tracing_interceptor( - address, credentials, options + address, credentials, options, interceptor ) @staticmethod @@ -1040,6 +1054,7 @@ def send_health_check_sync( target, tls=tls, root_certificates=root_certificates, + enable_trace=False, ) as channel: health_check_req = health_pb2.HealthCheckRequest() health_check_req.service = '' From b107f800b14a5f2ca0b727a851312e46ac9392f6 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 22 Sep 2022 15:40:47 +0200 Subject: [PATCH 15/79] feat(instrumentation): add InstrumentationMixIn for Head and Worker runtime --- jina/serve/runtimes/head/__init__.py | 4 +++- jina/serve/runtimes/worker/__init__.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/jina/serve/runtimes/head/__init__.py b/jina/serve/runtimes/head/__init__.py index 8912c40db393e..57adf85d21aaf 100644 --- a/jina/serve/runtimes/head/__init__.py +++ b/jina/serve/runtimes/head/__init__.py @@ -16,6 +16,7 @@ from jina.helper import get_full_version from jina.importer import ImportExtensions from jina.proto import jina_pb2, jina_pb2_grpc +from jina.serve.instrumentation import InstrumentationMixin from jina.serve.networking import GrpcConnectionPool from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime from jina.serve.runtimes.helper import _get_grpc_server_options @@ -143,7 +144,8 @@ def _default_polling_dict(self, default_polling): async def async_setup(self): """Wait for the GRPC server to start""" self._grpc_server = grpc.aio.server( - options=_get_grpc_server_options(self.args.grpc_server_options) + options=_get_grpc_server_options(self.args.grpc_server_options), + interceptors=[self.aio_tracing_server_interceptor()], ) jina_pb2_grpc.add_JinaSingleDataRequestRPCServicer_to_server( diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index 565f75d5ae0d9..6e8f45f4ce5d9 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -90,7 +90,8 @@ async def _async_setup_grpc_server(self): """ self._grpc_server = grpc.aio.server( - options=_get_grpc_server_options(self.args.grpc_server_options) + options=_get_grpc_server_options(self.args.grpc_server_options), + interceptors=[self.aio_tracing_server_interceptor()], ) jina_pb2_grpc.add_JinaSingleDataRequestRPCServicer_to_server( From cd175883c77cb8ab24975f980674a4fa1ba940d8 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 26 Sep 2022 15:47:31 +0200 Subject: [PATCH 16/79] feat(instrumentation): disable tracing of ServerReflection and endpoint discovery client requests --- extra-requirements.txt | 1 + jina/clients/base/grpc.py | 1 + jina/serve/networking.py | 11 +++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/extra-requirements.txt b/extra-requirements.txt index 191e902c6d84c..e39f5774056f1 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -85,3 +85,4 @@ bs4: cicd jsonschema: cicd portforward>=0.2.4: cicd tensorflow>=2.0: cicd +opentelemetry-test-utils>=0.33b0: test diff --git a/jina/clients/base/grpc.py b/jina/clients/base/grpc.py index 6f614d517fe44..f3dd3fa261fe6 100644 --- a/jina/clients/base/grpc.py +++ b/jina/clients/base/grpc.py @@ -31,6 +31,7 @@ async def _is_flow_ready(self, **kwargs) -> bool: f'{self.args.host}:{self.args.port}', asyncio=True, tls=self.args.tls, + enable_trace=False, ) as channel: stub = jina_pb2_grpc.JinaGatewayDryRunRPCStub(channel) self.logger.debug(f'connected to {self.args.host}:{self.args.port}') diff --git a/jina/serve/networking.py b/jina/serve/networking.py index ccd0013707e0b..1cf89a49f209a 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -140,7 +140,7 @@ def _create_connection(self, address): use_tls = parsed_address.scheme in TLS_PROTOCOL_SCHEMES stubs, channel = GrpcConnectionPool.create_async_channel_stub( - address, tls=use_tls, summary=self.summary + address, tls=use_tls, summary=self.summary, enable_trace=False ) return stubs, channel @@ -1148,7 +1148,11 @@ async def send_request_async( @staticmethod def create_async_channel_stub( - address, tls=False, root_certificates: Optional[str] = None, summary=None + address, + tls=False, + root_certificates: Optional[str] = None, + summary=None, + enable_trace: Optional[bool] = False, ) -> Tuple[ConnectionStubs, grpc.aio.Channel]: """ Creates an async GRPC Channel. This channel has to be closed eventually! @@ -1157,6 +1161,8 @@ def create_async_channel_stub( :param tls: if True, use tls for the grpc channel :param root_certificates: the path to the root certificates for tls, only u :param summary: Optional Prometheus summary object + :param enable_trace: If True the tracing interceptors are added to the channel otherwise the channel will not be provided with any tracing interceptors. + :param :returns: DataRequest stubs and an async grpc channel """ @@ -1165,6 +1171,7 @@ def create_async_channel_stub( asyncio=True, tls=tls, root_certificates=root_certificates, + enable_trace=enable_trace, ) return ( From 2e44270f3fe29b451de2f996f6cdf5e8d710b4a6 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 26 Sep 2022 15:48:49 +0200 Subject: [PATCH 17/79] test(instrumentation): add basic tracing and metrics tests for HTTP Gateway --- .../gateway/http/test_instrumentation.py | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 tests/unit/serve/runtimes/gateway/http/test_instrumentation.py diff --git a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py new file mode 100644 index 0000000000000..2b5970f934256 --- /dev/null +++ b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py @@ -0,0 +1,238 @@ +import json +import multiprocessing +import time +from typing import Dict, Optional, Sequence, Tuple + +import requests as req +from docarray import DocumentArray +from opentelemetry.context.context import Context +from opentelemetry.sdk.metrics._internal import MeterProvider +from opentelemetry.sdk.metrics._internal.export import ( + InMemoryMetricReader, + MetricExporter, + MetricExportResult, + MetricReader, +) +from opentelemetry.sdk.metrics.export import MetricsData, PeriodicExportingMetricReader +from opentelemetry.sdk.trace import ReadableSpan, TracerProvider, export +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult +from opentelemetry.test.test_base import TestBase + +from jina import Executor, Flow, requests + + +class ExecutorTestWithTracing(Executor): + def __init__( + self, + metas: Optional[Dict] = None, + requests: Optional[Dict] = None, + runtime_args: Optional[Dict] = None, + workspace: Optional[str] = None, + **kwargs, + ): + super().__init__(metas, requests, runtime_args, workspace, **kwargs) + self.docs_counter = self.meter.create_counter(name='docs_counter') + + @requests(on='/index') + def empty(self, docs: 'DocumentArray', otel_context: Context, **kwargs): + with self.tracer.start_as_current_span('dummy', context=otel_context) as span: + span.set_attribute('len_docs', len(docs)) + self.docs_counter.add(len(docs)) + return docs + + +class CustomSpanExporter(SpanExporter): + """Implementation of :class:`.SpanExporter` that stores spans as json in a multiprocessing.list(). + + This class can be used for testing purposes. It stores the exported spans + in a list in memory that can be retrieved using the + :func:`.get_finished_spans` method. + """ + + def __init__(self): + self._mp_manager = multiprocessing.Manager() + self._finished_spans = self._mp_manager.list() + + def clear(self): + """Clear list of collected spans.""" + self._finished_spans[:] = [] + + def get_finished_spans(self): + """Get list of collected spans.""" + return tuple(self._finished_spans) + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + """Stores a list of spans in memory.""" + for span in spans: + self._finished_spans.append(span.to_json(indent=None)) + return SpanExportResult.SUCCESS + + +class CustomMetricExporter(MetricExporter): + """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`. + There are two internal data value holders from the multiprocessing library. + The "self._collector" multiprocessing.Value type holds the latest metric export. This can be useful in some situations to check + the latest export but it can be overwritten easily by another operation or metric provider reset. + The "self._docs_count_data_points" holds the data points of a specific "docs_counter" metric. + + This is useful for e.g. unit tests. + """ + + def __init__( + self, + ): + super().__init__() + self._manager = multiprocessing.Manager() + self._collector = self._manager.Value('s', '') + self._docs_count_data_points = self._manager.list() + + def export( + self, + metrics_data: MetricsData, + timeout_millis: float = 500, + **kwargs, + ) -> MetricExportResult: + for resource_metrics in metrics_data.resource_metrics: + for scope_metrics in resource_metrics.scope_metrics: + for metric in scope_metrics.metrics: + if metric.name == 'docs_counter': + self._docs_count_data_points.append( + metric.data.to_json(indent=None) + ) + + self._collector.value = metrics_data.to_json(indent=None) + return MetricExportResult.SUCCESS + + def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: + pass + + def force_flush(self, timeout_millis: float = 10_000) -> bool: + return True + + def get_metrics_data(self): + return self._collector.value + + def get_docs_count_data_points(self): + return self._docs_count_data_points + + +class TestHttpGatewayTracing(TestBase): + custom_metric_exporter = CustomMetricExporter() + + def setUp(self): + super().setUp() + self.docs_input = {'data': [{'text': 'text_input'}]} + + def tearDown(self): + super().tearDown() + + @staticmethod + def create_tracer_provider(**kwargs): + """Helper to create a configured tracer provider. + + Creates and configures a `TracerProvider` with a + `SimpleSpanProcessor` and a `InMemorySpanExporter`. + All the parameters passed are forwarded to the TracerProvider + constructor. + + Returns: + A list with the tracer provider in the first element and the + in-memory span exporter in the second. + """ + + memory_exporter = CustomSpanExporter() + + tracer_provider = TracerProvider(**kwargs) + span_processor = export.SimpleSpanProcessor(memory_exporter) + tracer_provider.add_span_processor(span_processor) + + return tracer_provider, memory_exporter + + @staticmethod + def create_meter_provider( + **kwargs, + ) -> Tuple[MeterProvider, MetricReader]: + """Helper to create a configured meter provider + Creates a `MeterProvider` and an `InMemoryMetricReader`. + Returns: + A tuple with the meter provider in the first element and the + in-memory metrics exporter in the second + """ + memory_reader = InMemoryMetricReader() + custom_metric_reader = PeriodicExportingMetricReader( + TestHttpGatewayTracing.custom_metric_exporter, export_interval_millis=500 + ) + + metric_readers = kwargs.get("metric_readers", []) + metric_readers.append(memory_reader) + metric_readers.append(custom_metric_reader) + kwargs["metric_readers"] = metric_readers + meter_provider = MeterProvider(**kwargs) + return meter_provider, memory_reader + + def partition_spans_by_kind(self): + '''Returns three lists each containing spans of kind SpanKind.SERVER, SpanKind.CLIENT and SpandKind.INTERNAL''' + server_spans = [] + client_spans = [] + internal_spans = [] + + for span_json in self.memory_exporter.get_finished_spans(): + span = json.loads(span_json) + span_kind = span.get('kind', '') + if 'SpanKind.SERVER' == span_kind: + server_spans.append(span) + elif 'SpanKind.CLIENT' == span_kind: + client_spans.append(span) + elif 'SpanKind.INTERNAL' == span_kind: + internal_spans.append(span) + + return (server_spans, client_spans, internal_spans) + + def test_http_span_attributes_default_args(self): + f = Flow(protocol='http').add() + + with f: + req.post( + f'http://localhost:{f.port}/index', + json=self.docs_input, + ) + # give some time for the tracing and metrics exporters to finish exporting. + time.sleep(1) + + self.assertEqual(0, len(self.get_finished_spans())) + + def test_http_span_attributes_with_executor(self): + f = Flow( + protocol='http', opentelemetry_tracing=True, opentelemetry_metrics=True + ).add(uses=ExecutorTestWithTracing) + + with f: + req.post( + f'http://localhost:{f.port}/index', + json=self.docs_input, + ) + # give some time for the tracing and metrics exporters to finish exporting. + time.sleep(1) + + ( + server_spans, + client_spans, + internal_spans, + ) = self.partition_spans_by_kind() + self.assertEqual(4, len(internal_spans)) + for internal_span in internal_spans: + if internal_span.get('name', '') == 'dummy': + self.assertEqual( + len(self.docs_input), internal_span['attributes']['len_docs'] + ) + + self.assertEqual(5, len(server_spans)) + self.assertEqual(0, len(client_spans)) + self.assertEqual(9, len(self.get_finished_spans())) + + self.assertGreater( + len( + TestHttpGatewayTracing.custom_metric_exporter.get_docs_count_data_points() + ), + 0, + ) From a08314631adaec52e021075d9542faba5224450f Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 26 Sep 2022 15:53:08 +0200 Subject: [PATCH 18/79] test(instrumentation): move test common code for tracing and metrics to module file --- tests/unit/serve/runtimes/gateway/__init__.py | 109 +++++++++++++++++ .../gateway/http/test_instrumentation.py | 114 ++---------------- 2 files changed, 118 insertions(+), 105 deletions(-) diff --git a/tests/unit/serve/runtimes/gateway/__init__.py b/tests/unit/serve/runtimes/gateway/__init__.py index e69de29bb2d1d..f9021a4cbe4a1 100644 --- a/tests/unit/serve/runtimes/gateway/__init__.py +++ b/tests/unit/serve/runtimes/gateway/__init__.py @@ -0,0 +1,109 @@ +import multiprocessing +from typing import Dict, Optional, Sequence + +from docarray import DocumentArray +from opentelemetry.context.context import Context +from opentelemetry.sdk.metrics._internal.export import ( + MetricExporter, + MetricExportResult, +) +from opentelemetry.sdk.metrics.export import MetricsData +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult + +from jina import Executor, requests + + +class ExecutorTestWithTracing(Executor): + def __init__( + self, + metas: Optional[Dict] = None, + requests: Optional[Dict] = None, + runtime_args: Optional[Dict] = None, + workspace: Optional[str] = None, + **kwargs, + ): + super().__init__(metas, requests, runtime_args, workspace, **kwargs) + self.docs_counter = self.meter.create_counter(name='docs_counter') + + @requests(on='/index') + def empty(self, docs: 'DocumentArray', otel_context: Context, **kwargs): + with self.tracer.start_as_current_span('dummy', context=otel_context) as span: + span.set_attribute('len_docs', len(docs)) + self.docs_counter.add(len(docs)) + return docs + + +class CustomSpanExporter(SpanExporter): + """Implementation of :class:`.SpanExporter` that stores spans as json in a multiprocessing.list(). + + This class can be used for testing purposes. It stores the exported spans + in a list in memory that can be retrieved using the + :func:`.get_finished_spans` method. + """ + + def __init__(self): + self._mp_manager = multiprocessing.Manager() + self._finished_spans = self._mp_manager.list() + + def clear(self): + """Clear list of collected spans.""" + self._finished_spans[:] = [] + + def get_finished_spans(self): + """Get list of collected spans.""" + return tuple(self._finished_spans) + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + """Stores a list of spans in memory.""" + for span in spans: + self._finished_spans.append(span.to_json(indent=None)) + return SpanExportResult.SUCCESS + + +class CustomMetricExporter(MetricExporter): + """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`. + There are two internal data value holders from the multiprocessing library. + The "self._collector" multiprocessing.Value type holds the latest metric export. This can be useful in some situations to check + the latest export but it can be overwritten easily by another operation or metric provider reset. + The "self._docs_count_data_points" holds the data points of a specific "docs_counter" metric. + + This is useful for e.g. unit tests. + """ + + def __init__( + self, + ): + super().__init__() + self._manager = multiprocessing.Manager() + self._collector = self._manager.Value('s', '') + self._docs_count_data_points = self._manager.list() + + def export( + self, + metrics_data: MetricsData, + timeout_millis: float = 500, + **kwargs, + ) -> MetricExportResult: + for resource_metrics in metrics_data.resource_metrics: + for scope_metrics in resource_metrics.scope_metrics: + for metric in scope_metrics.metrics: + if metric.name == 'docs_counter': + self._docs_count_data_points.append( + metric.data.to_json(indent=None) + ) + + self._collector.value = metrics_data.to_json(indent=None) + return MetricExportResult.SUCCESS + + def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: + pass + + def force_flush(self, timeout_millis: float = 10_000) -> bool: + return True + + def get_metrics_data(self): + return self._collector.value + + def get_docs_count_data_points(self): + return self._docs_count_data_points diff --git a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py index 2b5970f934256..f32b657c749a6 100644 --- a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py +++ b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py @@ -1,119 +1,23 @@ import json -import multiprocessing import time -from typing import Dict, Optional, Sequence, Tuple +from typing import Tuple import requests as req -from docarray import DocumentArray -from opentelemetry.context.context import Context from opentelemetry.sdk.metrics._internal import MeterProvider from opentelemetry.sdk.metrics._internal.export import ( InMemoryMetricReader, - MetricExporter, - MetricExportResult, MetricReader, ) -from opentelemetry.sdk.metrics.export import MetricsData, PeriodicExportingMetricReader -from opentelemetry.sdk.trace import ReadableSpan, TracerProvider, export -from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult +from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader +from opentelemetry.sdk.trace import TracerProvider, export from opentelemetry.test.test_base import TestBase -from jina import Executor, Flow, requests - - -class ExecutorTestWithTracing(Executor): - def __init__( - self, - metas: Optional[Dict] = None, - requests: Optional[Dict] = None, - runtime_args: Optional[Dict] = None, - workspace: Optional[str] = None, - **kwargs, - ): - super().__init__(metas, requests, runtime_args, workspace, **kwargs) - self.docs_counter = self.meter.create_counter(name='docs_counter') - - @requests(on='/index') - def empty(self, docs: 'DocumentArray', otel_context: Context, **kwargs): - with self.tracer.start_as_current_span('dummy', context=otel_context) as span: - span.set_attribute('len_docs', len(docs)) - self.docs_counter.add(len(docs)) - return docs - - -class CustomSpanExporter(SpanExporter): - """Implementation of :class:`.SpanExporter` that stores spans as json in a multiprocessing.list(). - - This class can be used for testing purposes. It stores the exported spans - in a list in memory that can be retrieved using the - :func:`.get_finished_spans` method. - """ - - def __init__(self): - self._mp_manager = multiprocessing.Manager() - self._finished_spans = self._mp_manager.list() - - def clear(self): - """Clear list of collected spans.""" - self._finished_spans[:] = [] - - def get_finished_spans(self): - """Get list of collected spans.""" - return tuple(self._finished_spans) - - def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: - """Stores a list of spans in memory.""" - for span in spans: - self._finished_spans.append(span.to_json(indent=None)) - return SpanExportResult.SUCCESS - - -class CustomMetricExporter(MetricExporter): - """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`. - There are two internal data value holders from the multiprocessing library. - The "self._collector" multiprocessing.Value type holds the latest metric export. This can be useful in some situations to check - the latest export but it can be overwritten easily by another operation or metric provider reset. - The "self._docs_count_data_points" holds the data points of a specific "docs_counter" metric. - - This is useful for e.g. unit tests. - """ - - def __init__( - self, - ): - super().__init__() - self._manager = multiprocessing.Manager() - self._collector = self._manager.Value('s', '') - self._docs_count_data_points = self._manager.list() - - def export( - self, - metrics_data: MetricsData, - timeout_millis: float = 500, - **kwargs, - ) -> MetricExportResult: - for resource_metrics in metrics_data.resource_metrics: - for scope_metrics in resource_metrics.scope_metrics: - for metric in scope_metrics.metrics: - if metric.name == 'docs_counter': - self._docs_count_data_points.append( - metric.data.to_json(indent=None) - ) - - self._collector.value = metrics_data.to_json(indent=None) - return MetricExportResult.SUCCESS - - def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: - pass - - def force_flush(self, timeout_millis: float = 10_000) -> bool: - return True - - def get_metrics_data(self): - return self._collector.value - - def get_docs_count_data_points(self): - return self._docs_count_data_points +from jina import Flow +from tests.unit.serve.runtimes.gateway import ( + CustomMetricExporter, + CustomSpanExporter, + ExecutorTestWithTracing, +) class TestHttpGatewayTracing(TestBase): From 30ee9e3c412aba001abb277a4aa357a1c77945b6 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 26 Sep 2022 16:20:17 +0200 Subject: [PATCH 19/79] feat(instrumentation): enable tracing of flow internal and start up requests --- jina/serve/networking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jina/serve/networking.py b/jina/serve/networking.py index 1cf89a49f209a..ed5c5b2a11e72 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -140,7 +140,7 @@ def _create_connection(self, address): use_tls = parsed_address.scheme in TLS_PROTOCOL_SCHEMES stubs, channel = GrpcConnectionPool.create_async_channel_stub( - address, tls=use_tls, summary=self.summary, enable_trace=False + address, tls=use_tls, summary=self.summary, enable_trace=True ) return stubs, channel From 3998e2ff6c3ff3923ee5dca4d576721c3fcb4682 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 26 Sep 2022 16:30:02 +0200 Subject: [PATCH 20/79] test(instrumentation): move test common code to new base class --- tests/unit/serve/runtimes/gateway/__init__.py | 77 ++++++++++++++++- .../gateway/http/test_instrumentation.py | 84 ++----------------- 2 files changed, 79 insertions(+), 82 deletions(-) diff --git a/tests/unit/serve/runtimes/gateway/__init__.py b/tests/unit/serve/runtimes/gateway/__init__.py index f9021a4cbe4a1..15716e9966cd3 100644 --- a/tests/unit/serve/runtimes/gateway/__init__.py +++ b/tests/unit/serve/runtimes/gateway/__init__.py @@ -1,15 +1,20 @@ +import json import multiprocessing -from typing import Dict, Optional, Sequence +from typing import Dict, Optional, Sequence, Tuple from docarray import DocumentArray from opentelemetry.context.context import Context +from opentelemetry.sdk.metrics._internal import MeterProvider from opentelemetry.sdk.metrics._internal.export import ( + InMemoryMetricReader, MetricExporter, MetricExportResult, + MetricReader, ) -from opentelemetry.sdk.metrics.export import MetricsData -from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.metrics.export import MetricsData, PeriodicExportingMetricReader +from opentelemetry.sdk.trace import ReadableSpan, TracerProvider, export from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult +from opentelemetry.test.test_base import TestBase from jina import Executor, requests @@ -107,3 +112,69 @@ def get_metrics_data(self): def get_docs_count_data_points(self): return self._docs_count_data_points + + +class InstrumentationTestBase(TestBase): + custom_metric_exporter = CustomMetricExporter() + + @staticmethod + def create_tracer_provider(**kwargs): + """Helper to create a configured tracer provider. + + Creates and configures a `TracerProvider` with a + `SimpleSpanProcessor` and a `InMemorySpanExporter`. + All the parameters passed are forwarded to the TracerProvider + constructor. + + Returns: + A list with the tracer provider in the first element and the + in-memory span exporter in the second. + """ + + memory_exporter = CustomSpanExporter() + + tracer_provider = TracerProvider(**kwargs) + span_processor = export.SimpleSpanProcessor(memory_exporter) + tracer_provider.add_span_processor(span_processor) + + return tracer_provider, memory_exporter + + @staticmethod + def create_meter_provider( + **kwargs, + ) -> Tuple[MeterProvider, MetricReader]: + """Helper to create a configured meter provider + Creates a `MeterProvider` and an `InMemoryMetricReader`. + Returns: + A tuple with the meter provider in the first element and the + in-memory metrics exporter in the second + """ + memory_reader = InMemoryMetricReader() + custom_metric_reader = PeriodicExportingMetricReader( + InstrumentationTestBase.custom_metric_exporter, export_interval_millis=500 + ) + + metric_readers = kwargs.get("metric_readers", []) + metric_readers.append(memory_reader) + metric_readers.append(custom_metric_reader) + kwargs["metric_readers"] = metric_readers + meter_provider = MeterProvider(**kwargs) + return meter_provider, memory_reader + + def partition_spans_by_kind(self): + '''Returns three lists each containing spans of kind SpanKind.SERVER, SpanKind.CLIENT and SpandKind.INTERNAL''' + server_spans = [] + client_spans = [] + internal_spans = [] + + for span_json in self.memory_exporter.get_finished_spans(): + span = json.loads(span_json) + span_kind = span.get('kind', '') + if 'SpanKind.SERVER' == span_kind: + server_spans.append(span) + elif 'SpanKind.CLIENT' == span_kind: + client_spans.append(span) + elif 'SpanKind.INTERNAL' == span_kind: + internal_spans.append(span) + + return (server_spans, client_spans, internal_spans) diff --git a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py index f32b657c749a6..192cf46e58608 100644 --- a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py +++ b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py @@ -1,28 +1,16 @@ -import json import time -from typing import Tuple import requests as req -from opentelemetry.sdk.metrics._internal import MeterProvider -from opentelemetry.sdk.metrics._internal.export import ( - InMemoryMetricReader, - MetricReader, -) -from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader -from opentelemetry.sdk.trace import TracerProvider, export -from opentelemetry.test.test_base import TestBase from jina import Flow from tests.unit.serve.runtimes.gateway import ( CustomMetricExporter, - CustomSpanExporter, ExecutorTestWithTracing, + InstrumentationTestBase, ) -class TestHttpGatewayTracing(TestBase): - custom_metric_exporter = CustomMetricExporter() - +class TestHttpGatewayTracing(InstrumentationTestBase): def setUp(self): super().setUp() self.docs_input = {'data': [{'text': 'text_input'}]} @@ -30,68 +18,6 @@ def setUp(self): def tearDown(self): super().tearDown() - @staticmethod - def create_tracer_provider(**kwargs): - """Helper to create a configured tracer provider. - - Creates and configures a `TracerProvider` with a - `SimpleSpanProcessor` and a `InMemorySpanExporter`. - All the parameters passed are forwarded to the TracerProvider - constructor. - - Returns: - A list with the tracer provider in the first element and the - in-memory span exporter in the second. - """ - - memory_exporter = CustomSpanExporter() - - tracer_provider = TracerProvider(**kwargs) - span_processor = export.SimpleSpanProcessor(memory_exporter) - tracer_provider.add_span_processor(span_processor) - - return tracer_provider, memory_exporter - - @staticmethod - def create_meter_provider( - **kwargs, - ) -> Tuple[MeterProvider, MetricReader]: - """Helper to create a configured meter provider - Creates a `MeterProvider` and an `InMemoryMetricReader`. - Returns: - A tuple with the meter provider in the first element and the - in-memory metrics exporter in the second - """ - memory_reader = InMemoryMetricReader() - custom_metric_reader = PeriodicExportingMetricReader( - TestHttpGatewayTracing.custom_metric_exporter, export_interval_millis=500 - ) - - metric_readers = kwargs.get("metric_readers", []) - metric_readers.append(memory_reader) - metric_readers.append(custom_metric_reader) - kwargs["metric_readers"] = metric_readers - meter_provider = MeterProvider(**kwargs) - return meter_provider, memory_reader - - def partition_spans_by_kind(self): - '''Returns three lists each containing spans of kind SpanKind.SERVER, SpanKind.CLIENT and SpandKind.INTERNAL''' - server_spans = [] - client_spans = [] - internal_spans = [] - - for span_json in self.memory_exporter.get_finished_spans(): - span = json.loads(span_json) - span_kind = span.get('kind', '') - if 'SpanKind.SERVER' == span_kind: - server_spans.append(span) - elif 'SpanKind.CLIENT' == span_kind: - client_spans.append(span) - elif 'SpanKind.INTERNAL' == span_kind: - internal_spans.append(span) - - return (server_spans, client_spans, internal_spans) - def test_http_span_attributes_default_args(self): f = Flow(protocol='http').add() @@ -103,7 +29,7 @@ def test_http_span_attributes_default_args(self): # give some time for the tracing and metrics exporters to finish exporting. time.sleep(1) - self.assertEqual(0, len(self.get_finished_spans())) + self.assertEqual(4, len(self.get_finished_spans())) def test_http_span_attributes_with_executor(self): f = Flow( @@ -131,8 +57,8 @@ def test_http_span_attributes_with_executor(self): ) self.assertEqual(5, len(server_spans)) - self.assertEqual(0, len(client_spans)) - self.assertEqual(9, len(self.get_finished_spans())) + self.assertEqual(4, len(client_spans)) + self.assertEqual(13, len(self.get_finished_spans())) self.assertGreater( len( From 30409c2c88bc27947bc5ec499fd1ae87b460afa6 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 26 Sep 2022 16:32:09 +0200 Subject: [PATCH 21/79] test(instrumentation): test grpc gateway opentelemety instrumentation --- .../gateway/grpc/test_instrumentation.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py diff --git a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py new file mode 100644 index 0000000000000..acacd0f4e18d0 --- /dev/null +++ b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py @@ -0,0 +1,71 @@ +import time + +from jina import Client, Flow +from tests.unit.serve.runtimes.gateway import ( + ExecutorTestWithTracing, + InstrumentationTestBase, +) + + +class TestGrpcGatewayTracing(InstrumentationTestBase): + def setUp(self): + super().setUp() + self.docs_input = {'data': [{'text': 'text_input'}]} + + def tearDown(self): + super().tearDown() + + def test_http_span_attributes_default_args(self): + f = Flow(protocol='grpc').add() + + with f: + c = Client( + host=f'grpc://localhost:{f.port}', + ) + c.post( + f'/index', + self.docs_input, + ) + # give some time for the tracing and metrics exporters to finish exporting. + time.sleep(1) + + self.assertEqual(5, len(self.get_finished_spans())) + + def test_http_span_attributes_with_executor(self): + f = Flow( + protocol='grpc', opentelemetry_tracing=True, opentelemetry_metrics=True + ).add(uses=ExecutorTestWithTracing) + + with f: + c = Client( + host=f'grpc://localhost:{f.port}', + ) + c.post( + f'/index', + self.docs_input, + ) + # give some time for the tracing and metrics exporters to finish exporting. + time.sleep(1) + + ( + server_spans, + client_spans, + internal_spans, + ) = self.partition_spans_by_kind() + self.assertEqual(1, len(internal_spans)) + for internal_span in internal_spans: + if internal_span.get('name', '') == 'dummy': + self.assertEqual( + len(self.docs_input), internal_span['attributes']['len_docs'] + ) + + self.assertEqual(5, len(server_spans)) + self.assertEqual(5, len(client_spans)) + self.assertEqual(11, len(self.get_finished_spans())) + + self.assertGreater( + len( + TestGrpcGatewayTracing.custom_metric_exporter.get_docs_count_data_points() + ), + 0, + ) From e2ee86274d08d2e5bdce035d6b9c7e7a0b4e02f7 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 10:26:12 +0200 Subject: [PATCH 22/79] feat(instrumentation): add Jaeger export agent and required configuration --- extra-requirements.txt | 1 + jina/orchestrate/flow/base.py | 8 ++++++++ jina/parsers/client.py | 14 ++++++++++++++ jina/parsers/orchestrate/pod.py | 14 ++++++++++++++ jina/serve/instrumentation/__init__.py | 11 +++++++++-- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/extra-requirements.txt b/extra-requirements.txt index e39f5774056f1..12bcff4320078 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -45,6 +45,7 @@ opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel +opentelemetry-exporter-jaeger>=1.12.0: perf,standrad,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index 202b9598dcb07..ff3db315da990 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -157,6 +157,8 @@ def __init__( grpc_server_options: Optional[dict] = None, host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', + jaeger_host: Optional[str] = '0.0.0.0', + jaeger_port: Optional[int] = 6831, log_config: Optional[str] = None, monitoring: Optional[bool] = False, name: Optional[str] = 'gateway', @@ -210,6 +212,8 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 + :param jaeger_host: 'If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.' + :param jaeger_port: 'If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.' :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -807,6 +811,8 @@ def add( host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', install_requirements: Optional[bool] = False, + jaeger_host: Optional[str] = '0.0.0.0', + jaeger_port: Optional[int] = 6831, log_config: Optional[str] = None, monitoring: Optional[bool] = False, name: Optional[str] = None, @@ -871,6 +877,8 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local + :param jaeger_host: 'If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.' + :param jaeger_port: 'If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.' :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. diff --git a/jina/parsers/client.py b/jina/parsers/client.py index 79f59c8e65788..3a3be5f8a1f72 100644 --- a/jina/parsers/client.py +++ b/jina/parsers/client.py @@ -39,6 +39,20 @@ def mixin_client_features_parser(parser): 'Otherwise a no-op implementation will be provided.', ) + parser.add_argument( + '--jaeger-host', + type=str, + default='0.0.0.0', + help='If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.', + ) + + parser.add_argument( + '--jaeger-port', + type=int, + default=6831, + help='If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.', + ) + parser.add_argument( '--opentelemetry-metrics', action='store_true', diff --git a/jina/parsers/orchestrate/pod.py b/jina/parsers/orchestrate/pod.py index ce14e43048fe1..69fa629843d2d 100644 --- a/jina/parsers/orchestrate/pod.py +++ b/jina/parsers/orchestrate/pod.py @@ -128,6 +128,20 @@ def mixin_pod_parser(parser): 'Otherwise a no-op implementation will be provided.', ) + gp.add_argument( + '--jaeger-host', + type=str, + default='0.0.0.0', + help='If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.', + ) + + gp.add_argument( + '--jaeger-port', + type=int, + default=6831, + help='If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.', + ) + gp.add_argument( '--opentelemetry-metrics', action='store_true', diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 1579e1e4ac66a..ac04889323f81 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,4 +1,5 @@ from opentelemetry import metrics, trace +from opentelemetry.exporter.jaeger.thrift import JaegerExporter from opentelemetry.instrumentation.grpc import ( client_interceptor as grpc_client_interceptor, ) @@ -9,7 +10,7 @@ ) from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter +from opentelemetry.sdk.trace.export import BatchSpanProcessor from jina.serve.instrumentation._aio_client import ( StreamStreamAioClientInterceptor, @@ -33,8 +34,14 @@ def _setup_instrumentation(self) -> None: resource = Resource(attributes={SERVICE_NAME: name}) if self.args.opentelemetry_tracing: + print(f'--->{self.args.jaeger_host}:{self.args.jaeger_port}') provider = TracerProvider(resource=resource) - processor = BatchSpanProcessor(ConsoleSpanExporter()) + processor = BatchSpanProcessor( + JaegerExporter( + agent_host_name=self.args.jaeger_host, + agent_port=self.args.jaeger_port, + ) + ) provider.add_span_processor(processor) trace.set_tracer_provider(provider) self.tracer = trace.get_tracer(name) From a0bfaf802be4755310cf96127c415805042ca1da Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 10:36:03 +0200 Subject: [PATCH 23/79] chore(instrumentation): remove print statement --- jina/serve/instrumentation/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index ac04889323f81..99ead4b646819 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -34,7 +34,6 @@ def _setup_instrumentation(self) -> None: resource = Resource(attributes={SERVICE_NAME: name}) if self.args.opentelemetry_tracing: - print(f'--->{self.args.jaeger_host}:{self.args.jaeger_port}') provider = TracerProvider(resource=resource) processor = BatchSpanProcessor( JaegerExporter( From 60be04429328a5a16096aa4b974973294254cfeb Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 14:45:04 +0200 Subject: [PATCH 24/79] test(instrumentation): document spans in the grpc and http gateway instrumentation --- .../gateway/grpc/test_instrumentation.py | 30 +++++++++++++++-- .../gateway/http/test_instrumentation.py | 32 ++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py index acacd0f4e18d0..742c2d2e19e5a 100644 --- a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py +++ b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py @@ -15,6 +15,12 @@ def setUp(self): def tearDown(self): super().tearDown() + @staticmethod + def print_spans(spans): + for span in spans: + print(f'--->span: {span}') + print() + def test_http_span_attributes_default_args(self): f = Flow(protocol='grpc').add() @@ -28,8 +34,22 @@ def test_http_span_attributes_default_args(self): ) # give some time for the tracing and metrics exporters to finish exporting. time.sleep(1) - - self.assertEqual(5, len(self.get_finished_spans())) + ( + server_spans, + client_spans, + internal_spans, + ) = self.partition_spans_by_kind() + self.assertEqual(0, len(server_spans)) + # There are currently 5 SpanKind.CLIENT spans produced by the + # a. /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo (2) + # b. /jina.JinaDiscoverEndpointsRPC/endpoint_discovery (1) + # c. /jina.JinaSingleDataRequestRPC/process_single_data (1) + # d. /jina.JinaRPC/Call (1) + # that cannot yet be completely disabled because the GrpcConnectionPool.get_grpc_channel + # method is a static method. This means that until a concrete '.set_tracer_provider' gets executed, the tracer will + # be a default ProxyTracerProvider. + self.assertEqual(5, len(client_spans)) + self.assertEqual(0, len(internal_spans)) def test_http_span_attributes_with_executor(self): f = Flow( @@ -52,6 +72,8 @@ def test_http_span_attributes_with_executor(self): client_spans, internal_spans, ) = self.partition_spans_by_kind() + + # There only 1 dummy span created in the Executor method self.assertEqual(1, len(internal_spans)) for internal_span in internal_spans: if internal_span.get('name', '') == 'dummy': @@ -59,8 +81,10 @@ def test_http_span_attributes_with_executor(self): len(self.docs_input), internal_span['attributes']['len_docs'] ) - self.assertEqual(5, len(server_spans)) + # The 5 spans are as described in the above test. self.assertEqual(5, len(client_spans)) + # The SpanKind.SERVER spans for each of the above 5 SpanKind.CLIENT requests + self.assertEqual(5, len(server_spans)) self.assertEqual(11, len(self.get_finished_spans())) self.assertGreater( diff --git a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py index 192cf46e58608..98326d304bb7d 100644 --- a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py +++ b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py @@ -4,7 +4,6 @@ from jina import Flow from tests.unit.serve.runtimes.gateway import ( - CustomMetricExporter, ExecutorTestWithTracing, InstrumentationTestBase, ) @@ -28,13 +27,27 @@ def test_http_span_attributes_default_args(self): ) # give some time for the tracing and metrics exporters to finish exporting. time.sleep(1) - + ( + server_spans, + client_spans, + internal_spans, + ) = self.partition_spans_by_kind() + self.assertEqual(0, len(server_spans)) + # There are currently 4 SpanKind.CLIENT spans produced by the + # a. /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo (2) + # b. /jina.JinaDiscoverEndpointsRPC/endpoint_discovery (1) + # c. /jina.JinaSingleDataRequestRPC/process_single_data (1) + # that cannot yet be completely disabled because the GrpcConnectionPool.get_grpc_channel + # method is a static method. This means that until a concrete '.set_tracer_provider' gets executed, the tracer will + # be a default ProxyTracerProvider. + self.assertEqual(4, len(client_spans)) + self.assertEqual(0, len(internal_spans)) self.assertEqual(4, len(self.get_finished_spans())) def test_http_span_attributes_with_executor(self): f = Flow( protocol='http', opentelemetry_tracing=True, opentelemetry_metrics=True - ).add(uses=ExecutorTestWithTracing) + ).add(uses=ExecutorTestWithTracing, name='executortest') with f: req.post( @@ -49,6 +62,7 @@ def test_http_span_attributes_with_executor(self): client_spans, internal_spans, ) = self.partition_spans_by_kind() + self.assertEqual(4, len(internal_spans)) for internal_span in internal_spans: if internal_span.get('name', '') == 'dummy': @@ -56,8 +70,18 @@ def test_http_span_attributes_with_executor(self): len(self.docs_input), internal_span['attributes']['len_docs'] ) - self.assertEqual(5, len(server_spans)) + # There are the usual 4 SpanKind.CLIENT spans as mentioned above. self.assertEqual(4, len(client_spans)) + # There are 5 total spans. The CLIENT spans from the above are being traced correctly by the server. Apart + # from the spans mentioned in te above test, there is an extra span (expected) for the '/index' + # request from the client that is handled by the gateway http server> + self.assertEqual(5, len(server_spans)) + # The FASTApi app instrumentation tracks the server spans as SpanKind.INTERNAL. There are 4 spans for: + # 1. /index http receive + # 2. dummy operation in the Executor method + # 3. /index http send + # 4. /index http send + self.assertEqual(4, len(internal_spans)) self.assertEqual(13, len(self.get_finished_spans())) self.assertGreater( From adb96baa6cb0a0a8044f82281c86b4ab41ab6e69 Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Tue, 27 Sep 2022 13:53:24 +0000 Subject: [PATCH 25/79] style: fix overload and cli autocomplete --- docs/fundamentals/flow/executor-args.md | 4 +++ docs/fundamentals/flow/gateway-args.md | 6 +++- jina/clients/__init__.py | 14 +++++++-- jina/orchestrate/flow/base.py | 40 ++++++++++++++++++------- jina/resources/extra-requirements.txt | 4 +++ jina_cli/autocomplete.py | 20 +++++++++++++ 6 files changed, 74 insertions(+), 14 deletions(-) diff --git a/docs/fundamentals/flow/executor-args.md b/docs/fundamentals/flow/executor-args.md index 9502cf34d82d0..02b3ba0c00c74 100644 --- a/docs/fundamentals/flow/executor-args.md +++ b/docs/fundamentals/flow/executor-args.md @@ -35,6 +35,10 @@ | `port_monitoring` | The port on which the prometheus server is exposed, default is a random port between [49152, 65535] | `string` | `random in [49152, 65535]` | | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | +| `opentelemetry_tracing` | If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `jaeger_host` | If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. | `string` | `0.0.0.0` | +| `jaeger_port` | If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. | `number` | `6831` | +| `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `install_requirements` | If set, install `requirements.txt` in the Hub Executor bundle to local | `boolean` | `False` | | `force_update` | If set, always pull the latest Hub Executor bundle even it exists on local | `boolean` | `False` | | `compression` | The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. | `string` | `None` | diff --git a/docs/fundamentals/flow/gateway-args.md b/docs/fundamentals/flow/gateway-args.md index ea33e2a32a00c..d1ec68e24e0d8 100644 --- a/docs/fundamentals/flow/gateway-args.md +++ b/docs/fundamentals/flow/gateway-args.md @@ -46,4 +46,8 @@ | `monitoring` | If set, spawn an http server with a prometheus endpoint to expose metrics | `boolean` | `False` | | `port_monitoring` | The port on which the prometheus server is exposed, default is a random port between [49152, 65535] | `string` | `random in [49152, 65535]` | | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | -| `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | \ No newline at end of file +| `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | +| `opentelemetry_tracing` | If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `jaeger_host` | If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. | `string` | `0.0.0.0` | +| `jaeger_port` | If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. | `number` | `6831` | +| `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | \ No newline at end of file diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 58566e3e7f51b..4b1741bcd26dc 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -20,8 +20,10 @@ def Client( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', - opentelemetry_tracing: Optional[bool] = False, + jaeger_host: Optional[str] = '0.0.0.0', + jaeger_port: Optional[int] = 6831, opentelemetry_metrics: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, @@ -39,8 +41,10 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param opentelemetry_tracing: 'If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided.' - :param opentelemetry_metrics: 'If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided.' + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy @@ -85,6 +89,10 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index 9c8ae05e78718..c3163c02ecb81 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -115,6 +115,10 @@ def __init__( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', + jaeger_host: Optional[str] = '0.0.0.0', + jaeger_port: Optional[int] = 6831, + opentelemetry_metrics: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, @@ -125,6 +129,10 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy @@ -165,8 +173,8 @@ def __init__( native: Optional[bool] = False, no_crud_endpoints: Optional[bool] = False, no_debug_endpoints: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, opentelemetry_metrics: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, output_array_type: Optional[str] = None, polling: Optional[str] = 'ANY', port: Optional[int] = None, @@ -212,8 +220,8 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 - :param jaeger_host: 'If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.' - :param jaeger_port: 'If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.' + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -230,8 +238,8 @@ def __init__( Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. - :param opentelemetry_tracing: 'If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided.' - :param opentelemetry_metrics: 'If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided.' + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -372,6 +380,10 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy @@ -391,6 +403,8 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -407,6 +421,8 @@ def __init__( Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -817,8 +833,8 @@ def add( monitoring: Optional[bool] = False, name: Optional[str] = None, native: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, opentelemetry_metrics: Optional[bool] = False, + opentelemetry_tracing: Optional[bool] = False, output_array_type: Optional[str] = None, polling: Optional[str] = 'ANY', port: Optional[int] = None, @@ -877,8 +893,8 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local - :param jaeger_host: 'If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.' - :param jaeger_port: 'If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.' + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -891,8 +907,8 @@ def add( When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. - :param opentelemetry_tracing: 'If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided.' - :param opentelemetry_metrics: 'If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided.' + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -1030,6 +1046,8 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local + :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. + :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -1042,6 +1060,8 @@ def add( When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. + :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. + :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found diff --git a/jina/resources/extra-requirements.txt b/jina/resources/extra-requirements.txt index f665e4bf51cf3..f9a7e9d891210 100644 --- a/jina/resources/extra-requirements.txt +++ b/jina/resources/extra-requirements.txt @@ -43,6 +43,9 @@ opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel +opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel +opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel +opentelemetry-exporter-jaeger>=1.12.0: perf,standrad,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel @@ -83,3 +86,4 @@ bs4: cicd jsonschema: cicd portforward>=0.2.4: cicd tensorflow>=2.0: cicd +opentelemetry-test-utils>=0.33b0: test diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index a95ef2180efec..3f54783de95cf 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -62,6 +62,10 @@ '--port-monitoring', '--retries', '--floating', + '--opentelemetry-tracing', + '--jaeger-host', + '--jaeger-port', + '--opentelemetry-metrics', '--install-requirements', '--force-update', '--force', @@ -148,6 +152,10 @@ '--port-monitoring', '--retries', '--floating', + '--opentelemetry-tracing', + '--jaeger-host', + '--jaeger-port', + '--opentelemetry-metrics', ], 'auth login': ['--help', '--force'], 'auth logout': ['--help'], @@ -258,6 +266,10 @@ '--port-monitoring', '--retries', '--floating', + '--opentelemetry-tracing', + '--jaeger-host', + '--jaeger-port', + '--opentelemetry-metrics', '--install-requirements', '--force-update', '--force', @@ -312,6 +324,10 @@ '--port-monitoring', '--retries', '--floating', + '--opentelemetry-tracing', + '--jaeger-host', + '--jaeger-port', + '--opentelemetry-metrics', '--install-requirements', '--force-update', '--force', @@ -335,6 +351,10 @@ '--port', '--tls', '--asyncio', + '--opentelemetry-tracing', + '--jaeger-host', + '--jaeger-port', + '--opentelemetry-metrics', '--protocol', ], }, From 0af8ffb89504db734c84272a1585c6b2e0e49e66 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 15:59:30 +0200 Subject: [PATCH 26/79] chore: remove print statement --- .../serve/runtimes/gateway/grpc/test_instrumentation.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py index 742c2d2e19e5a..f845a702a7f4e 100644 --- a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py +++ b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py @@ -15,12 +15,6 @@ def setUp(self): def tearDown(self): super().tearDown() - @staticmethod - def print_spans(spans): - for span in spans: - print(f'--->span: {span}') - print() - def test_http_span_attributes_default_args(self): f = Flow(protocol='grpc').add() From a241f6228a9525d84f402798e488ed1ce2e22532 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 16:08:11 +0200 Subject: [PATCH 27/79] test(instrumentation): add instrumentaiton tests for websocket gateway --- .../runtimes/gateway/websocket/__init__.py | 0 .../gateway/websocket/test_instrumentation.py | 92 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/unit/serve/runtimes/gateway/websocket/__init__.py create mode 100644 tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py diff --git a/tests/unit/serve/runtimes/gateway/websocket/__init__.py b/tests/unit/serve/runtimes/gateway/websocket/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py new file mode 100644 index 0000000000000..6e2b536f934be --- /dev/null +++ b/tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py @@ -0,0 +1,92 @@ +import time + +from jina import Client, Flow +from tests.unit.serve.runtimes.gateway import ( + ExecutorTestWithTracing, + InstrumentationTestBase, +) + + +class TestWebsocketGatewayTracing(InstrumentationTestBase): + def setUp(self): + super().setUp() + self.docs_input = {'data': [{'text': 'text_input'}]} + + def tearDown(self): + super().tearDown() + + def test_http_span_attributes_default_args(self): + f = Flow(protocol='websocket').add() + + with f: + c = Client( + host=f'ws://localhost:{f.port}', + ) + c.post( + f'/index', + self.docs_input, + ) + # give some time for the tracing and metrics exporters to finish exporting. + time.sleep(1) + ( + server_spans, + client_spans, + internal_spans, + ) = self.partition_spans_by_kind() + self.assertEqual(0, len(server_spans)) + # There are currently 5 SpanKind.CLIENT spans produced by the + # a. /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo (2) + # b. /jina.JinaDiscoverEndpointsRPC/endpoint_discovery (1) + # c. /jina.JinaSingleDataRequestRPC/process_single_data (1) + # d. HTTP GET (1) + # that cannot yet be completely disabled because the GrpcConnectionPool.get_grpc_channel + # method is a static method. This means that until a concrete '.set_tracer_provider' gets executed, the tracer will + # be a default ProxyTracerProvider. + self.assertEqual(5, len(client_spans)) + self.assertEqual(0, len(internal_spans)) + + def test_http_span_attributes_with_executor(self): + f = Flow( + protocol='websocket', opentelemetry_tracing=True, opentelemetry_metrics=True + ).add(uses=ExecutorTestWithTracing) + + with f: + c = Client( + host=f'ws://localhost:{f.port}', + ) + c.post( + f'/index', + self.docs_input, + ) + # give some time for the tracing and metrics exporters to finish exporting. + time.sleep(1) + + ( + server_spans, + client_spans, + internal_spans, + ) = self.partition_spans_by_kind() + + # There are 6 SpanKind.INTERNAL spans created for + # a. / websocket receive (3) + # b. / websocket send (2) + # c. dummy (1) span created by the Executor + self.assertEqual(6, len(internal_spans)) + for internal_span in internal_spans: + if internal_span.get('name', '') == 'dummy': + self.assertEqual( + len(self.docs_input), internal_span['attributes']['len_docs'] + ) + + # The 5 spans are as described in the above test. + self.assertEqual(5, len(client_spans)) + # The SpanKind.SERVER spans for each of the above 5 SpanKind.CLIENT requests + self.assertEqual(5, len(server_spans)) + self.assertEqual(16, len(self.get_finished_spans())) + + self.assertGreater( + len( + TestWebsocketGatewayTracing.custom_metric_exporter.get_docs_count_data_points() + ), + 0, + ) From 528e38bc2be53b4ddd3b1653362ce52b87aac664 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 16:36:45 +0200 Subject: [PATCH 28/79] fix: import openetelmetry api globally and the other dependencies only when needed --- extra-requirements.txt | 2 +- jina/serve/instrumentation/__init__.py | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/extra-requirements.txt b/extra-requirements.txt index f9a7e9d891210..eb5d2da9649cc 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -36,13 +36,13 @@ docarray>=0.16.4: core jina-hubble-sdk>=0.16.1: core jcloud>=0.0.35: core opentelemetry-api>=1.12.0: core +opentelemetry-instrumentation-grpc>=0.33b0: core uvloop: perf,standard,devel prometheus_client: perf,standard,devel opentelemetry-sdk>=1.12.0: perf,standard,devel opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel -opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel opentelemetry-exporter-jaeger>=1.12.0: perf,standrad,devel diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 99ead4b646819..e7bb8add1d860 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,16 +1,7 @@ from opentelemetry import metrics, trace -from opentelemetry.exporter.jaeger.thrift import JaegerExporter from opentelemetry.instrumentation.grpc import ( client_interceptor as grpc_client_interceptor, ) -from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export import ( - ConsoleMetricExporter, - PeriodicExportingMetricReader, -) -from opentelemetry.sdk.resources import SERVICE_NAME, Resource -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor from jina.serve.instrumentation._aio_client import ( StreamStreamAioClientInterceptor, @@ -31,9 +22,14 @@ def _setup_instrumentation(self) -> None: name = self.__class__.__name__ if hasattr(self, 'name') and self.name: name = self.name - resource = Resource(attributes={SERVICE_NAME: name}) if self.args.opentelemetry_tracing: + from opentelemetry.exporter.jaeger.thrift import JaegerExporter + from opentelemetry.sdk.resources import SERVICE_NAME, Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + + resource = Resource(attributes={SERVICE_NAME: name}) provider = TracerProvider(resource=resource) processor = BatchSpanProcessor( JaegerExporter( @@ -46,6 +42,15 @@ def _setup_instrumentation(self) -> None: self.tracer = trace.get_tracer(name) if self.args.opentelemetry_metrics: + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.sdk.metrics.export import ( + ConsoleMetricExporter, + PeriodicExportingMetricReader, + ) + from opentelemetry.sdk.resources import SERVICE_NAME, Resource + + resource = Resource(attributes={SERVICE_NAME: name}) + metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) meter_provider = MeterProvider( metric_readers=[metric_reader], resource=resource From 47ed0a8349c4ea8ceb30201e0206ea6b92063cd8 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 17:21:50 +0200 Subject: [PATCH 29/79] fix: use class name as default name when creating Executor instrumentation providers --- jina/serve/executors/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index ca7ac3a70dac5..36f637c5227aa 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -183,8 +183,12 @@ def _init_monitoring(self): self._metrics_buffer = None def _init_instrumentation(self): - self.tracer = trace.get_tracer(self.runtime_args.name) - self.meter = metrics.get_meter(self.runtime_args.name) + name = self.__class__.__name__ + if hasattr(self.runtime_args, 'name'): + name = self.runtime_args.name + + self.tracer = trace.get_tracer(name) + self.meter = metrics.get_meter(name) def _add_requests(self, _requests: Optional[Dict]): if not hasattr(self, 'requests'): From 3f436dafc65f76a85a3ab99ddf4acca0fd5ace79 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 27 Sep 2022 17:31:22 +0200 Subject: [PATCH 30/79] fix: provide argparse arguments to AlternativeGateway --- .../test_clients_post_extra_kwargs.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py b/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py index e6a67499a65d6..0459a7602e5ad 100644 --- a/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py +++ b/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py @@ -40,8 +40,10 @@ async def intercept_service(self, continuation, handler_call_details): return self._deny class AlternativeGRPCGateway(GRPCGateway): - def __init__(self, *args, **kwargs): - super(AlternativeGRPCGateway, self).__init__(*args, **kwargs) + def __init__(self, namespace_args, *args, **kwargs): + super(AlternativeGRPCGateway, self).__init__( + args=namespace_args, *args, **kwargs + ) self.server = grpc.aio.server( interceptors=(AuthInterceptor('access_key'),), options=_get_grpc_server_options(self.grpc_server_options), @@ -62,6 +64,7 @@ async def async_setup(self): self.gateway = AlternativeGRPCGateway( name=self.name, + namespace_args=self.args, grpc_server_options=self.args.grpc_server_options, port=self.args.port, ssl_keyfile=self.args.ssl_keyfile, @@ -69,7 +72,6 @@ async def async_setup(self): ) self.gateway.set_streamer( - args=self.args, timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.name, From 578e8824f04d694cdd67ad899831f30714e616cc Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Tue, 27 Sep 2022 15:35:44 +0000 Subject: [PATCH 31/79] style: fix overload and cli autocomplete --- jina/resources/extra-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jina/resources/extra-requirements.txt b/jina/resources/extra-requirements.txt index f9a7e9d891210..eb5d2da9649cc 100644 --- a/jina/resources/extra-requirements.txt +++ b/jina/resources/extra-requirements.txt @@ -36,13 +36,13 @@ docarray>=0.16.4: core jina-hubble-sdk>=0.16.1: core jcloud>=0.0.35: core opentelemetry-api>=1.12.0: core +opentelemetry-instrumentation-grpc>=0.33b0: core uvloop: perf,standard,devel prometheus_client: perf,standard,devel opentelemetry-sdk>=1.12.0: perf,standard,devel opentelemetry-exporter-otlp>=1.12.0: perf,standard,devel opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel -opentelemetry-instrumentation-grpc>=0.33b0: perf,standard,devel opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel opentelemetry-exporter-jaeger>=1.12.0: perf,standrad,devel From aa5a34a4f16a0151e51a0c1c22962d49acfa7f11 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 28 Sep 2022 09:39:08 +0200 Subject: [PATCH 32/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 3f54783de95cf..d6394d7f0f7fd 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -199,7 +199,6 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], - 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -212,7 +211,6 @@ '--version', '--loglevel', 'login', - 'logout', 'deploy', 'list', 'logs', From f7b4af498731374fe4cc8af139df76e2fd859e83 Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Wed, 28 Sep 2022 07:49:26 +0000 Subject: [PATCH 33/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index d6394d7f0f7fd..3f54783de95cf 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -199,6 +199,7 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], + 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -211,6 +212,7 @@ '--version', '--loglevel', 'login', + 'logout', 'deploy', 'list', 'logs', From 3a2e1deb0361ebe8e1aa9642ed78cc25e5e290d8 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 28 Sep 2022 09:50:57 +0200 Subject: [PATCH 34/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 3f54783de95cf..d6394d7f0f7fd 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -199,7 +199,6 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], - 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -212,7 +211,6 @@ '--version', '--loglevel', 'login', - 'logout', 'deploy', 'list', 'logs', From 82dad9c3c93a9edeec2bfc17a55e90ca8ca697db Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Wed, 28 Sep 2022 07:52:13 +0000 Subject: [PATCH 35/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index d6394d7f0f7fd..3f54783de95cf 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -199,6 +199,7 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], + 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -211,6 +212,7 @@ '--version', '--loglevel', 'login', + 'logout', 'deploy', 'list', 'logs', From 42d00e64e002b959e6a3d6a6ed05bc5ab3a7dcb5 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 29 Sep 2022 16:31:42 +0200 Subject: [PATCH 36/79] fix: revert changes for Gateway implementation --- jina/serve/gateway.py | 23 ++++----- jina/serve/runtimes/gateway/__init__.py | 51 +++++++++++++++++++ jina/serve/runtimes/gateway/grpc/__init__.py | 2 +- jina/serve/runtimes/gateway/grpc/gateway.py | 9 +--- jina/serve/runtimes/gateway/http/__init__.py | 2 +- jina/serve/runtimes/gateway/http/app.py | 7 --- jina/serve/runtimes/gateway/http/gateway.py | 7 +-- jina/serve/runtimes/gateway/http/models.py | 8 +-- .../runtimes/gateway/websocket/__init__.py | 2 +- jina/serve/runtimes/gateway/websocket/app.py | 7 --- .../runtimes/gateway/websocket/gateway.py | 7 +-- 11 files changed, 73 insertions(+), 52 deletions(-) diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index a881ca5387da3..7c83cb6ac4c88 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -4,7 +4,6 @@ from jina.jaml import JAMLCompatible from jina.logging.logger import JinaLogger -from jina.serve.instrumentation import InstrumentationMixin from jina.serve.streamer import GatewayStreamer __all__ = ['BaseGateway'] @@ -13,7 +12,7 @@ from prometheus_client import CollectorRegistry -class BaseGateway(JAMLCompatible, InstrumentationMixin): +class BaseGateway(JAMLCompatible): """ The base class of all custom Gateways, can be used to build a custom interface to a Jina Flow that supports gateway logic @@ -23,30 +22,28 @@ class BaseGateway(JAMLCompatible, InstrumentationMixin): def __init__( self, - args: 'argparse.Namespace', name: Optional[str] = 'gateway', **kwargs, ): """ - :param args: args created by parsing the arguments :param name: Gateway pod name :param kwargs: additional extra keyword arguments to avoid failing when extra params ara passed that are not expected """ - self.args = args self.streamer = None self.name = name # TODO: original implementation also passes args, maybe move this to a setter/initializer func self.logger = JinaLogger(self.name) - super().__init__() def set_streamer( self, + args: 'argparse.Namespace' = None, timeout_send: Optional[float] = None, metrics_registry: Optional['CollectorRegistry'] = None, runtime_name: Optional[str] = None, ): """ Set streamer object by providing runtime parameters. + :param args: runtime args :param timeout_send: grpc connection timeout :param metrics_registry: metric registry when monitoring is enabled :param runtime_name: name of the runtime providing the streamer @@ -55,10 +52,10 @@ def set_streamer( from jina.serve.streamer import GatewayStreamer - graph_description = json.loads(self.args.graph_description) - graph_conditions = json.loads(self.args.graph_conditions) - deployments_addresses = json.loads(self.args.deployments_addresses) - deployments_disable_reduce = json.loads(self.args.deployments_disable_reduce) + graph_description = json.loads(args.graph_description) + graph_conditions = json.loads(args.graph_conditions) + deployments_addresses = json.loads(args.deployments_addresses) + deployments_disable_reduce = json.loads(args.deployments_disable_reduce) self.streamer = GatewayStreamer( graph_representation=graph_description, @@ -66,10 +63,10 @@ def set_streamer( graph_conditions=graph_conditions, deployments_disable_reduce=deployments_disable_reduce, timeout_send=timeout_send, - retries=self.args.retries, - compression=self.args.compression, + retries=args.retries, + compression=args.compression, runtime_name=runtime_name, - prefetch=self.args.prefetch, + prefetch=args.prefetch, logger=self.logger, metrics_registry=metrics_registry, ) diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 591f7628089e2..9f2e6c854aafc 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -1,5 +1,7 @@ import argparse +import urllib from abc import ABC +from http import HTTPStatus from typing import TYPE_CHECKING, Optional, Union from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime @@ -28,3 +30,52 @@ def __init__( if self.timeout_send: self.timeout_send /= 1e3 # convert ms to seconds super().__init__(args, cancel_event, **kwargs) + + @staticmethod + def is_ready(ctrl_address: str, protocol: Optional[str] = 'grpc', **kwargs) -> bool: + """ + Check if status is ready. + + :param ctrl_address: the address where the control request needs to be sent + :param protocol: protocol of the gateway runtime + :param kwargs: extra keyword arguments + + :return: True if status is ready else False. + """ + + if protocol is None or protocol == 'grpc': + res = AsyncNewLoopRuntime.is_ready(ctrl_address) + else: + try: + conn = urllib.request.urlopen(url=f'http://{ctrl_address}') + res = conn.code == HTTPStatus.OK + except: + res = False + return res + + @classmethod + def wait_for_ready_or_shutdown( + cls, + timeout: Optional[float], + ready_or_shutdown_event: Union['multiprocessing.Event', 'threading.Event'], + ctrl_address: str, + protocol: Optional[str] = 'grpc', + **kwargs, + ): + """ + Check if the runtime has successfully started + + :param timeout: The time to wait before readiness or failure is determined + :param ctrl_address: the address where the control message needs to be sent + :param ready_or_shutdown_event: the multiprocessing event to detect if the process failed or is ready + :param protocol: protocol of the gateway runtime + :param kwargs: extra keyword arguments + + :return: True if is ready or it needs to be shutdown + """ + return super().wait_for_ready_or_shutdown( + timeout=timeout, + ready_or_shutdown_event=ready_or_shutdown_event, + ctrl_address=ctrl_address, + protocol=protocol, + ) diff --git a/jina/serve/runtimes/gateway/grpc/__init__.py b/jina/serve/runtimes/gateway/grpc/__init__.py index c5e4916eb91b9..644cc624ec514 100644 --- a/jina/serve/runtimes/gateway/grpc/__init__.py +++ b/jina/serve/runtimes/gateway/grpc/__init__.py @@ -27,7 +27,6 @@ async def async_setup(self): self.gateway = GRPCGateway( name=self.name, - args=self.args, grpc_server_options=self.args.grpc_server_options, port=self.args.port, ssl_keyfile=self.args.ssl_keyfile, @@ -35,6 +34,7 @@ async def async_setup(self): ) self.gateway.set_streamer( + args=self.args, timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.name, diff --git a/jina/serve/runtimes/gateway/grpc/gateway.py b/jina/serve/runtimes/gateway/grpc/gateway.py index b9401bd8bc746..22013691e20b4 100644 --- a/jina/serve/runtimes/gateway/grpc/gateway.py +++ b/jina/serve/runtimes/gateway/grpc/gateway.py @@ -1,4 +1,3 @@ -import argparse from typing import Optional import grpc @@ -19,7 +18,6 @@ class GRPCGateway(BaseGateway): def __init__( self, - args: 'argparse.Namespace', port: Optional[int] = None, grpc_server_options: Optional[dict] = None, ssl_keyfile: Optional[str] = None, @@ -27,22 +25,19 @@ def __init__( **kwargs, ): """Initialize the gateway - :param args: runtime args :param port: The port of the Gateway, which the client should connect to. :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param ssl_keyfile: the path to the key file :param ssl_certfile: the path to the certificate file :param kwargs: keyword args """ - super().__init__(args=args, **kwargs) - self._setup_instrumentation() + super().__init__(**kwargs) self.port = port self.grpc_server_options = grpc_server_options self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.server = grpc.aio.server( - options=_get_grpc_server_options(self.grpc_server_options), - interceptors=[self.aio_tracing_server_interceptor()], + options=_get_grpc_server_options(self.grpc_server_options) ) self.health_servicer = health.HealthServicer(experimental_non_blocking=True) diff --git a/jina/serve/runtimes/gateway/http/__init__.py b/jina/serve/runtimes/gateway/http/__init__.py index bd89c4ba878e8..0cb2903194f9d 100644 --- a/jina/serve/runtimes/gateway/http/__init__.py +++ b/jina/serve/runtimes/gateway/http/__init__.py @@ -21,7 +21,6 @@ async def async_setup(self): """ self.gateway = HTTPGateway( name=self.name, - args=self.args, port=self.args.port, title=self.args.title, description=self.args.description, @@ -36,6 +35,7 @@ async def async_setup(self): ) self.gateway.set_streamer( + args=self.args, timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.args.name, diff --git a/jina/serve/runtimes/gateway/http/app.py b/jina/serve/runtimes/gateway/http/app.py index cabf7037817a9..2ff5f9175d714 100644 --- a/jina/serve/runtimes/gateway/http/app.py +++ b/jina/serve/runtimes/gateway/http/app.py @@ -2,8 +2,6 @@ import json from typing import TYPE_CHECKING, Dict, List, Optional -from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor - from jina import __version__ from jina.clients.request import request_generator from jina.enums import DataInputType @@ -17,7 +15,6 @@ def get_fastapi_app( - args: 'argparse.Namespace', streamer: 'GatewayStreamer', title: str, description: str, @@ -31,7 +28,6 @@ def get_fastapi_app( """ Get the app from FastAPI as the REST interface. - :param args: runtime args :param streamer: gateway streamer object :param title: The title of this HTTP server. It will be used in automatics docs such as Swagger UI. :param description: The description of this HTTP server. It will be used in automatics docs such as Swagger UI. @@ -63,9 +59,6 @@ def get_fastapi_app( version=__version__, ) - if args.opentelemetry_tracing: - FastAPIInstrumentor.instrument_app(app) - if cors: app.add_middleware( CORSMiddleware, diff --git a/jina/serve/runtimes/gateway/http/gateway.py b/jina/serve/runtimes/gateway/http/gateway.py index 86715aa77b51f..1d6ab534af742 100644 --- a/jina/serve/runtimes/gateway/http/gateway.py +++ b/jina/serve/runtimes/gateway/http/gateway.py @@ -1,4 +1,3 @@ -import argparse import logging import os from typing import Optional @@ -15,7 +14,6 @@ class HTTPGateway(BaseGateway): def __init__( self, - args: 'argparse.Namespace', port: Optional[int] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -31,7 +29,6 @@ def __init__( ): """Initialize the gateway Get the app from FastAPI as the REST interface. - :param args: runtime args :param port: The port of the Gateway, which the client should connect to. :param title: The title of this HTTP server. It will be used in automatics docs such as Swagger UI. :param description: The description of this HTTP server. It will be used in automatics docs such as Swagger UI. @@ -47,7 +44,7 @@ def __init__( :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server :param kwargs: keyword args """ - super().__init__(args=args, **kwargs) + super().__init__(**kwargs) self.port = port self.title = title self.description = description @@ -59,7 +56,6 @@ def __init__( self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.uvicorn_kwargs = uvicorn_kwargs - self._setup_instrumentation() async def setup_server(self): """ @@ -69,7 +65,6 @@ async def setup_server(self): self.app = extend_rest_interface( get_fastapi_app( - args=self.args, streamer=self.streamer, title=self.title, description=self.description, diff --git a/jina/serve/runtimes/gateway/http/models.py b/jina/serve/runtimes/gateway/http/models.py index 19d60ece3a157..bfcd5b7258a93 100644 --- a/jina/serve/runtimes/gateway/http/models.py +++ b/jina/serve/runtimes/gateway/http/models.py @@ -2,15 +2,17 @@ from datetime import datetime from enum import Enum from types import SimpleNamespace -from typing import Callable, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union from docarray.document.pydantic_model import PydanticDocument, PydanticDocumentArray from google.protobuf.descriptor import Descriptor, FieldDescriptor -from google.protobuf.pyext.cpp_message import GeneratedProtocolMessageType from pydantic import BaseConfig, BaseModel, Field, create_model, root_validator from jina.proto.jina_pb2 import DataRequestProto, JinaInfoProto, RouteProto, StatusProto +if TYPE_CHECKING: + from google.protobuf.pyext.cpp_message import GeneratedProtocolMessageType + PROTO_TO_PYDANTIC_MODELS = SimpleNamespace() PROTOBUF_TO_PYTHON_TYPE = { FieldDescriptor.TYPE_INT32: int, @@ -97,7 +99,7 @@ def oneof_setter(cls, values): def protobuf_to_pydantic_model( - protobuf_model: Union[Descriptor, GeneratedProtocolMessageType] + protobuf_model: Union[Descriptor, 'GeneratedProtocolMessageType'] ) -> BaseModel: """ Converts Protobuf messages to Pydantic model for jsonschema creation/validattion diff --git a/jina/serve/runtimes/gateway/websocket/__init__.py b/jina/serve/runtimes/gateway/websocket/__init__.py index 1cf109a50fa47..3f132f73c164a 100644 --- a/jina/serve/runtimes/gateway/websocket/__init__.py +++ b/jina/serve/runtimes/gateway/websocket/__init__.py @@ -20,7 +20,6 @@ async def async_setup(self): self.gateway = WebSocketGateway( name=self.name, - args=self.args, port=self.args.port, ssl_keyfile=self.args.ssl_keyfile, ssl_certfile=self.args.ssl_certfile, @@ -29,6 +28,7 @@ async def async_setup(self): ) self.gateway.set_streamer( + args=self.args, timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.args.name, diff --git a/jina/serve/runtimes/gateway/websocket/app.py b/jina/serve/runtimes/gateway/websocket/app.py index 40b9ee0f6c8da..2793e18898b51 100644 --- a/jina/serve/runtimes/gateway/websocket/app.py +++ b/jina/serve/runtimes/gateway/websocket/app.py @@ -1,8 +1,6 @@ import argparse from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Union -from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor - from jina.clients.request import request_generator from jina.enums import DataInputType, WebsocketSubProtocols from jina.excepts import InternalNetworkError @@ -24,14 +22,12 @@ def _fits_ws_close_msg(msg: str): def get_fastapi_app( - args: 'argparse.Namespace', streamer: 'GatewayStreamer', logger: 'JinaLogger', ): """ Get the app from FastAPI as the Websocket interface. - :param args: runtime args :param streamer: gateway streamer object. :param logger: Jina logger. :return: fastapi app @@ -113,9 +109,6 @@ async def send( app = FastAPI() - if args.opentelemetry_tracing: - FastAPIInstrumentor.instrument_app(app) - @app.get( path='/', summary='Get the health of Jina service', diff --git a/jina/serve/runtimes/gateway/websocket/gateway.py b/jina/serve/runtimes/gateway/websocket/gateway.py index 31b62ee56c83d..7295adf486aa0 100644 --- a/jina/serve/runtimes/gateway/websocket/gateway.py +++ b/jina/serve/runtimes/gateway/websocket/gateway.py @@ -1,4 +1,3 @@ -import argparse import logging import os from typing import Optional @@ -15,7 +14,6 @@ class WebSocketGateway(BaseGateway): def __init__( self, - args: 'argparse.Namespace', port: Optional[int] = None, ssl_keyfile: Optional[str] = None, ssl_certfile: Optional[str] = None, @@ -23,19 +21,17 @@ def __init__( **kwargs ): """Initialize the gateway - :param args: runtime args :param port: The port of the Gateway, which the client should connect to. :param ssl_keyfile: the path to the key file :param ssl_certfile: the path to the certificate file :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server :param kwargs: keyword args """ - super().__init__(args=args, **kwargs) + super().__init__(**kwargs) self.port = port self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.uvicorn_kwargs = uvicorn_kwargs - self._setup_instrumentation() async def setup_server(self): """ @@ -45,7 +41,6 @@ async def setup_server(self): self.app = extend_rest_interface( get_fastapi_app( - args=self.args, streamer=self.streamer, logger=self.logger, ) From 4132396688a33911982e3de588eff17e18785b2d Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 29 Sep 2022 16:38:50 +0200 Subject: [PATCH 37/79] feat(instrumentation): remove init method from InstrumentationMixin --- jina/serve/instrumentation/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index e7bb8add1d860..5ebe01cf77a35 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -14,10 +14,6 @@ class InstrumentationMixin: '''Instrumentation mixin for OpenTelemetery Tracing and Metrics handling''' - def __init__(self) -> None: - self.tracer = trace.NoOpTracer() - self.meter = metrics.NoOpMeter(name='no-op') - def _setup_instrumentation(self) -> None: name = self.__class__.__name__ if hasattr(self, 'name') and self.name: @@ -38,8 +34,11 @@ def _setup_instrumentation(self) -> None: ) ) provider.add_span_processor(processor) - trace.set_tracer_provider(provider) - self.tracer = trace.get_tracer(name) + self.tracer_provider = provider + self.tracer = provider.get_tracer(name) + else: + self.tracer_provider = trace.NoOpTracerProvider() + self.tracer = trace.NoOpTracer() if self.args.opentelemetry_metrics: from opentelemetry.sdk.metrics import MeterProvider @@ -55,8 +54,11 @@ def _setup_instrumentation(self) -> None: meter_provider = MeterProvider( metric_readers=[metric_reader], resource=resource ) - metrics.set_meter_provider(meter_provider) - self.meter = metrics.get_meter(name) + self.metrics_provider = meter_provider + self.meter = self.metrics_provider.get_meter(name) + else: + self.metrics_provider = metrics.NoOpMeterProvider() + self.meter = metrics.NoOpMeter() def aio_tracing_server_interceptor(self): '''Create a gRPC aio server interceptor. From 4efbbd71a0d45927157437a090fd65d59118819c Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 29 Sep 2022 17:03:32 +0200 Subject: [PATCH 38/79] feat(instrumentation): create vendor neutral opentelemetry export arguments --- jina/clients/__init__.py | 18 ++++++--- jina/orchestrate/flow/base.py | 54 +++++++++++++++++--------- jina/parsers/client.py | 26 ++++++++++--- jina/parsers/orchestrate/pod.py | 32 ++++++++++----- jina/serve/instrumentation/__init__.py | 4 +- jina_cli/autocomplete.py | 12 +----- 6 files changed, 94 insertions(+), 52 deletions(-) diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 4b1741bcd26dc..48947f1ee1dca 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -20,8 +20,10 @@ def Client( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', - jaeger_host: Optional[str] = '0.0.0.0', - jaeger_port: Optional[int] = 6831, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, + metrics_exporter_host: Optional[str] = None, + metrics_exporter_port: Optional[int] = None, opentelemetry_metrics: Optional[bool] = False, opentelemetry_tracing: Optional[bool] = False, port: Optional[int] = None, @@ -41,8 +43,10 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. @@ -89,8 +93,10 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_host: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index c3163c02ecb81..11a87e9fa5624 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -115,8 +115,10 @@ def __init__( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', - jaeger_host: Optional[str] = '0.0.0.0', - jaeger_port: Optional[int] = 6831, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, + metrics_exporter_host: Optional[str] = None, + metrics_exporter_port: Optional[int] = None, opentelemetry_metrics: Optional[bool] = False, opentelemetry_tracing: Optional[bool] = False, port: Optional[int] = None, @@ -129,8 +131,10 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. @@ -165,8 +169,10 @@ def __init__( grpc_server_options: Optional[dict] = None, host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', - jaeger_host: Optional[str] = '0.0.0.0', - jaeger_port: Optional[int] = 6831, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, + metrics_exporter_host: Optional[str] = None, + metrics_exporter_port: Optional[int] = None, log_config: Optional[str] = None, monitoring: Optional[bool] = False, name: Optional[str] = 'gateway', @@ -220,8 +226,10 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -380,8 +388,10 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. @@ -403,8 +413,10 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -827,8 +839,10 @@ def add( host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', install_requirements: Optional[bool] = False, - jaeger_host: Optional[str] = '0.0.0.0', - jaeger_port: Optional[int] = 6831, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, + metrics_exporter_host: Optional[str] = None, + metrics_exporter_port: Optional[int] = None, log_config: Optional[str] = None, monitoring: Optional[bool] = False, name: Optional[str] = None, @@ -893,8 +907,10 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -1046,9 +1062,11 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local - :param jaeger_host: If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. - :param jaeger_port: If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param log_config: The YAML config of the logger used in this object. + :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. + :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. diff --git a/jina/parsers/client.py b/jina/parsers/client.py index 3a3be5f8a1f72..8fb28b2c31166 100644 --- a/jina/parsers/client.py +++ b/jina/parsers/client.py @@ -40,17 +40,17 @@ def mixin_client_features_parser(parser): ) parser.add_argument( - '--jaeger-host', + '--span-exporter-host', type=str, - default='0.0.0.0', - help='If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.', + default=None, + help='If tracing is enabled, this hostname will be used to configure the trace exporter agent.', ) parser.add_argument( - '--jaeger-port', + '--span-exporter-port', type=int, - default=6831, - help='If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.', + default=None, + help='If tracing is enabled, this port will be used to configure the trace exporter agent.', ) parser.add_argument( @@ -60,3 +60,17 @@ def mixin_client_features_parser(parser): help='If set, real implementation of the metrics will be available for default monitoring and custom measurements. ' 'Otherwise a no-op implementation will be provided.', ) + + parser.add_argument( + '--metrics-exporter-host', + type=str, + default=None, + help='If tracing is enabled, this hostname will be used to configure the metrics exporter agent.', + ) + + parser.add_argument( + '--metrics-exporter-port', + type=int, + default=None, + help='If tracing is enabled, this port will be used to configure the metrics exporter agent.', + ) diff --git a/jina/parsers/orchestrate/pod.py b/jina/parsers/orchestrate/pod.py index 69fa629843d2d..7f80cc91f40ed 100644 --- a/jina/parsers/orchestrate/pod.py +++ b/jina/parsers/orchestrate/pod.py @@ -128,24 +128,38 @@ def mixin_pod_parser(parser): 'Otherwise a no-op implementation will be provided.', ) - gp.add_argument( - '--jaeger-host', + parser.add_argument( + '--span-exporter-host', type=str, - default='0.0.0.0', - help='If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent.', + default=None, + help='If tracing is enabled, this hostname will be used to configure the trace exporter agent.', ) - gp.add_argument( - '--jaeger-port', + parser.add_argument( + '--span-exporter-port', type=int, - default=6831, - help='If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent.', + default=None, + help='If tracing is enabled, this port will be used to configure the trace exporter agent.', ) - gp.add_argument( + parser.add_argument( '--opentelemetry-metrics', action='store_true', default=False, help='If set, real implementation of the metrics will be available for default monitoring and custom measurements. ' 'Otherwise a no-op implementation will be provided.', ) + + parser.add_argument( + '--metrics-exporter-host', + type=str, + default=None, + help='If tracing is enabled, this hostname will be used to configure the metrics exporter agent.', + ) + + parser.add_argument( + '--metrics-exporter-port', + type=int, + default=None, + help='If tracing is enabled, this port will be used to configure the metrics exporter agent.', + ) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 5ebe01cf77a35..3e17deabbb7d4 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -29,8 +29,8 @@ def _setup_instrumentation(self) -> None: provider = TracerProvider(resource=resource) processor = BatchSpanProcessor( JaegerExporter( - agent_host_name=self.args.jaeger_host, - agent_port=self.args.jaeger_port, + agent_host_name=self.args.span_exporter_host, + agent_port=self.args.span_exporter_port, ) ) provider.add_span_processor(processor) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 56f905008ff6f..d6394d7f0f7fd 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -88,15 +88,7 @@ '--env', '--inspect', ], - 'ping': [ - '--help', - 'flow', - 'executor', - 'gateway', - '--timeout', - '--attempts', - '--min-successful-attempts', - ], + 'ping': ['--help', 'flow', 'executor', '--timeout', '--retries'], 'export flowchart': ['--help', '--vertical-layout'], 'export kubernetes': ['--help', '--k8s-namespace'], 'export docker-compose': ['--help', '--network_name'], @@ -207,7 +199,6 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], - 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -220,7 +211,6 @@ '--version', '--loglevel', 'login', - 'logout', 'deploy', 'list', 'logs', From 8e9abcb84d436e7fc16cde5f0f48202abbc3cbfa Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 29 Sep 2022 17:35:05 +0200 Subject: [PATCH 39/79] style: fix overload and cli autocomplete --- docs/fundamentals/flow/executor-args.md | 6 ++-- docs/fundamentals/flow/gateway-args.md | 8 +++-- jina/clients/__init__.py | 12 +++---- jina/orchestrate/flow/base.py | 46 ++++++++++++------------- jina_cli/autocomplete.py | 40 +++++++++++++++------ 5 files changed, 67 insertions(+), 45 deletions(-) diff --git a/docs/fundamentals/flow/executor-args.md b/docs/fundamentals/flow/executor-args.md index 02b3ba0c00c74..17f234dc66ff3 100644 --- a/docs/fundamentals/flow/executor-args.md +++ b/docs/fundamentals/flow/executor-args.md @@ -36,9 +36,11 @@ | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | | `opentelemetry_tracing` | If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | -| `jaeger_host` | If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. | `string` | `0.0.0.0` | -| `jaeger_port` | If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. | `number` | `6831` | +| `span_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | +| `span_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | | `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `metrics_exporter_host` | If tracing is enabled, this hostname will be used to configure the metrics exporter agent. | `string` | `None` | +| `metrics_exporter_port` | If tracing is enabled, this port will be used to configure the metrics exporter agent. | `number` | `None` | | `install_requirements` | If set, install `requirements.txt` in the Hub Executor bundle to local | `boolean` | `False` | | `force_update` | If set, always pull the latest Hub Executor bundle even it exists on local | `boolean` | `False` | | `compression` | The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. | `string` | `None` | diff --git a/docs/fundamentals/flow/gateway-args.md b/docs/fundamentals/flow/gateway-args.md index d1ec68e24e0d8..26b91540378d3 100644 --- a/docs/fundamentals/flow/gateway-args.md +++ b/docs/fundamentals/flow/gateway-args.md @@ -48,6 +48,8 @@ | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | | `opentelemetry_tracing` | If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | -| `jaeger_host` | If tracing is enabled, this hostname will be used to configure the Jaeger trace exporter agent. | `string` | `0.0.0.0` | -| `jaeger_port` | If tracing is enabled, this port will be used to configure the Jaeger trace exporter agent. | `number` | `6831` | -| `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | \ No newline at end of file +| `span_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | +| `span_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | +| `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `metrics_exporter_host` | If tracing is enabled, this hostname will be used to configure the metrics exporter agent. | `string` | `None` | +| `metrics_exporter_port` | If tracing is enabled, this port will be used to configure the metrics exporter agent. | `number` | `None` | \ No newline at end of file diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 48947f1ee1dca..429fda338e281 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -20,8 +20,6 @@ def Client( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, opentelemetry_metrics: Optional[bool] = False, @@ -29,6 +27,8 @@ def Client( port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, tls: Optional[bool] = False, **kwargs ) -> Union[ @@ -43,8 +43,6 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. @@ -52,6 +50,8 @@ def Client( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption :return: the new Client object @@ -93,8 +93,6 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_host: If tracing is enabled, this port will be used to configure the trace exporter agent. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. @@ -102,6 +100,8 @@ def Client( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption :return: the new Client object diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index 11a87e9fa5624..5378093994773 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -115,8 +115,6 @@ def __init__( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, opentelemetry_metrics: Optional[bool] = False, @@ -124,6 +122,8 @@ def __init__( port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, tls: Optional[bool] = False, **kwargs, ): @@ -131,8 +131,6 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. @@ -140,6 +138,8 @@ def __init__( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption .. # noqa: DAR202 @@ -169,11 +169,9 @@ def __init__( grpc_server_options: Optional[dict] = None, host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, + log_config: Optional[str] = None, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, - log_config: Optional[str] = None, monitoring: Optional[bool] = False, name: Optional[str] = 'gateway', native: Optional[bool] = False, @@ -195,6 +193,8 @@ def __init__( retries: Optional[int] = -1, runtime_cls: Optional[str] = 'GRPCGatewayRuntime', shards: Optional[int] = 1, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, ssl_certfile: Optional[str] = None, ssl_keyfile: Optional[str] = None, timeout_ctrl: Optional[int] = 60, @@ -226,11 +226,9 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param log_config: The YAML config of the logger used in this object. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -280,6 +278,8 @@ def __init__( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param ssl_certfile: the path to the certificate file :param ssl_keyfile: the path to the key file :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever @@ -388,8 +388,6 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. @@ -397,6 +395,8 @@ def __init__( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. :param cors: If set, a CORS middleware is added to FastAPI frontend to allow cross-origin access. @@ -413,11 +413,9 @@ def __init__( :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param log_config: The YAML config of the logger used in this object. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -467,6 +465,8 @@ def __init__( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param ssl_certfile: the path to the certificate file :param ssl_keyfile: the path to the key file :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever @@ -839,11 +839,9 @@ def add( host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', install_requirements: Optional[bool] = False, - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, + log_config: Optional[str] = None, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, - log_config: Optional[str] = None, monitoring: Optional[bool] = False, name: Optional[str] = None, native: Optional[bool] = False, @@ -861,6 +859,8 @@ def add( retries: Optional[int] = -1, runtime_cls: Optional[str] = 'WorkerRuntime', shards: Optional[int] = 1, + span_exporter_host: Optional[str] = None, + span_exporter_port: Optional[int] = None, timeout_ctrl: Optional[int] = 60, timeout_ready: Optional[int] = 600000, timeout_send: Optional[int] = None, @@ -907,11 +907,9 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. + :param log_config: The YAML config of the logger used in this object. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param log_config: The YAML config of the logger used in this object. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics :param name: The name of this object. @@ -953,6 +951,8 @@ def add( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default @@ -1062,8 +1062,6 @@ def add( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param log_config: The YAML config of the logger used in this object. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. @@ -1108,6 +1106,8 @@ def add( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies + :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index d6394d7f0f7fd..3839d3e936db2 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -63,9 +63,11 @@ '--retries', '--floating', '--opentelemetry-tracing', - '--jaeger-host', - '--jaeger-port', + '--span-exporter-host', + '--span-exporter-port', '--opentelemetry-metrics', + '--metrics-exporter-host', + '--metrics-exporter-port', '--install-requirements', '--force-update', '--force', @@ -88,7 +90,15 @@ '--env', '--inspect', ], - 'ping': ['--help', 'flow', 'executor', '--timeout', '--retries'], + 'ping': [ + '--help', + 'flow', + 'executor', + 'gateway', + '--timeout', + '--attempts', + '--min-successful-attempts', + ], 'export flowchart': ['--help', '--vertical-layout'], 'export kubernetes': ['--help', '--k8s-namespace'], 'export docker-compose': ['--help', '--network_name'], @@ -153,9 +163,11 @@ '--retries', '--floating', '--opentelemetry-tracing', - '--jaeger-host', - '--jaeger-port', + '--span-exporter-host', + '--span-exporter-port', '--opentelemetry-metrics', + '--metrics-exporter-host', + '--metrics-exporter-port', ], 'auth login': ['--help', '--force'], 'auth logout': ['--help'], @@ -265,9 +277,11 @@ '--retries', '--floating', '--opentelemetry-tracing', - '--jaeger-host', - '--jaeger-port', + '--span-exporter-host', + '--span-exporter-port', '--opentelemetry-metrics', + '--metrics-exporter-host', + '--metrics-exporter-port', '--install-requirements', '--force-update', '--force', @@ -323,9 +337,11 @@ '--retries', '--floating', '--opentelemetry-tracing', - '--jaeger-host', - '--jaeger-port', + '--span-exporter-host', + '--span-exporter-port', '--opentelemetry-metrics', + '--metrics-exporter-host', + '--metrics-exporter-port', '--install-requirements', '--force-update', '--force', @@ -350,9 +366,11 @@ '--tls', '--asyncio', '--opentelemetry-tracing', - '--jaeger-host', - '--jaeger-port', + '--span-exporter-host', + '--span-exporter-port', '--opentelemetry-metrics', + '--metrics-exporter-host', + '--metrics-exporter-port', '--protocol', ], }, From 8eed2117e160928e9b8357a4d04bc45178d1e079 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 30 Sep 2022 13:44:13 +0200 Subject: [PATCH 40/79] feat(instrumentation): inject tracing variables from AsyncLoopRuntime or BaseClient --- jina/clients/base/__init__.py | 12 +++- jina/clients/base/grpc.py | 5 +- jina/clients/base/helper.py | 6 +- jina/clients/base/http.py | 53 ++++++++++------ jina/clients/base/websocket.py | 19 +++++- jina/serve/executors/__init__.py | 23 ++++--- jina/serve/gateway.py | 10 ++- jina/serve/instrumentation/__init__.py | 52 +++++++++++----- jina/serve/networking.py | 61 ++++++++++++------- jina/serve/runtimes/asyncio.py | 10 ++- jina/serve/runtimes/gateway/grpc/__init__.py | 7 +++ jina/serve/runtimes/gateway/grpc/gateway.py | 7 ++- jina/serve/runtimes/gateway/http/__init__.py | 8 +++ jina/serve/runtimes/gateway/http/app.py | 11 ++++ jina/serve/runtimes/gateway/http/gateway.py | 10 +++ .../runtimes/gateway/websocket/__init__.py | 8 +++ jina/serve/runtimes/gateway/websocket/app.py | 11 ++++ .../runtimes/gateway/websocket/gateway.py | 10 +++ .../request_handlers/data_request_handler.py | 25 ++++++-- jina/serve/runtimes/worker/__init__.py | 6 +- jina/serve/streamer.py | 25 +++++++- 21 files changed, 293 insertions(+), 86 deletions(-) diff --git a/jina/clients/base/__init__.py b/jina/clients/base/__init__.py index dc2bdbbca7c2a..cb172f2b824be 100644 --- a/jina/clients/base/__init__.py +++ b/jina/clients/base/__init__.py @@ -48,7 +48,17 @@ def __init__( os.unsetenv('https_proxy') self._inputs = None send_telemetry_event(event='start', obj=self) - self._setup_instrumentation() + self._setup_instrumentation( + name=self.args.name + if hasattr(self.args, 'name') + else self.__class__.__name__, + opentelemetry_tracing=self.args.opentelemetry_tracing, + span_exporter_host=self.args.span_exporter_host, + span_exporter_port=self.args.span_exporter_port, + opentelemetry_metrics=self.args.opentelemetry_metrics, + metrics_exporter_host=self.args.metrics_exporter_host, + metrics_exporter_port=self.args.metrics_exporter_port, + ) @staticmethod def check_input(inputs: Optional['InputType'] = None, **kwargs) -> None: diff --git a/jina/clients/base/grpc.py b/jina/clients/base/grpc.py index c2cc937c64beb..2a5d947baffe0 100644 --- a/jina/clients/base/grpc.py +++ b/jina/clients/base/grpc.py @@ -1,5 +1,6 @@ import asyncio import json +from cgitb import enable from typing import TYPE_CHECKING, Optional import grpc @@ -32,7 +33,6 @@ async def _is_flow_ready(self, **kwargs) -> bool: f'{self.args.host}:{self.args.port}', asyncio=True, tls=self.args.tls, - enable_trace=False, ) as channel: stub = jina_pb2_grpc.JinaGatewayDryRunRPCStub(channel) self.logger.debug(f'connected to {self.args.host}:{self.args.port}') @@ -113,6 +113,9 @@ async def _get_results( options=options, asyncio=True, tls=self.args.tls, + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( + self.tracer + ), ) as channel: stub = jina_pb2_grpc.JinaRPCStub(channel) self.logger.debug(f'connected to {self.args.host}:{self.args.port}') diff --git a/jina/clients/base/helper.py b/jina/clients/base/helper.py index 85ba2e16a7d5c..7749e3dd8993f 100644 --- a/jina/clients/base/helper.py +++ b/jina/clients/base/helper.py @@ -30,6 +30,7 @@ def __init__( initial_backoff: float = 0.5, max_backoff: float = 0.1, backoff_multiplier: float = 1.5, + tracer_provider: Optional[trace.TracerProvider] = None, **kwargs, ) -> None: """HTTP Client to be used with the streamer @@ -40,15 +41,14 @@ def __init__( :param initial_backoff: The first retry will happen with a delay of random(0, initial_backoff) :param max_backoff: The maximum accepted backoff after the exponential incremental delay :param backoff_multiplier: The n-th attempt will occur at random(0, min(initialBackoff*backoffMultiplier**(n-1), maxBackoff)) + :param tracer_provider: Optional tracer_provider that will be used to configure aiohttp tracing. :param kwargs: kwargs which will be forwarded to the `aiohttp.Session` instance. Used to pass headers to requests """ self.url = url self.logger = logger self.msg_recv = 0 self.msg_sent = 0 - self._trace_config = create_trace_config( - tracer_provider=trace.get_tracer_provider(), - ) + self._trace_config = create_trace_config(tracer_provider=tracer_provider) self.session = None self._session_kwargs = {} if kwargs.get('headers', None): diff --git a/jina/clients/base/http.py b/jina/clients/base/http.py index b78c033d0716a..dfbd8c3f39d61 100644 --- a/jina/clients/base/http.py +++ b/jina/clients/base/http.py @@ -25,19 +25,19 @@ def _handle_response_status(self, r_status, r_str, url): if r_status == status.HTTP_404_NOT_FOUND: raise BadClient(f'no such endpoint {url}') elif ( - r_status == status.HTTP_503_SERVICE_UNAVAILABLE - or r_status == status.HTTP_504_GATEWAY_TIMEOUT + r_status == status.HTTP_503_SERVICE_UNAVAILABLE + or r_status == status.HTTP_504_GATEWAY_TIMEOUT ): if ( - 'header' in r_str - and 'status' in r_str['header'] - and 'description' in r_str['header']['status'] + 'header' in r_str + and 'status' in r_str['header'] + and 'description' in r_str['header']['status'] ): raise ConnectionError(r_str['header']['status']['description']) else: raise ValueError(r_str) elif ( - r_status < status.HTTP_200_OK or r_status > status.HTTP_300_MULTIPLE_CHOICES + r_status < status.HTTP_200_OK or r_status > status.HTTP_300_MULTIPLE_CHOICES ): # failure codes raise ValueError(r_str) @@ -54,7 +54,12 @@ async def _is_flow_ready(self, **kwargs) -> bool: proto = 'https' if self.args.tls else 'http' url = f'{proto}://{self.args.host}:{self.args.port}/dry_run' iolet = await stack.enter_async_context( - HTTPClientlet(url=url, logger=self.logger, **kwargs) + HTTPClientlet( + url=url, + logger=self.logger, + tracer_provider=self.tracer_provider, + **kwargs, + ) ) response = await iolet.send_dry_run(**kwargs) @@ -75,16 +80,16 @@ async def _is_flow_ready(self, **kwargs) -> bool: return False async def _get_results( - self, - inputs: 'InputType', - on_done: 'CallbackFnType', - on_error: Optional['CallbackFnType'] = None, - on_always: Optional['CallbackFnType'] = None, - max_attempts: int = 1, - initial_backoff: float = 0.5, - max_backoff: float = 0.1, - backoff_multiplier: float = 1.5, - **kwargs, + self, + inputs: 'InputType', + on_done: 'CallbackFnType', + on_error: Optional['CallbackFnType'] = None, + on_always: Optional['CallbackFnType'] = None, + max_attempts: int = 1, + initial_backoff: float = 0.5, + max_backoff: float = 0.1, + backoff_multiplier: float = 1.5, + **kwargs, ): """ :param inputs: the callable @@ -113,12 +118,20 @@ async def _get_results( proto = 'https' if self.args.tls else 'http' url = f'{proto}://{self.args.host}:{self.args.port}/post' iolet = await stack.enter_async_context( - HTTPClientlet(url=url, logger=self.logger, max_attempts=max_attempts, initial_backoff=initial_backoff, - max_backoff=max_backoff, backoff_multiplier=backoff_multiplier, **kwargs) + HTTPClientlet( + url=url, + logger=self.logger, + tracer_provider=self.tracer_provider, + max_attempts=max_attempts, + initial_backoff=initial_backoff, + max_backoff=max_backoff, + backoff_multiplier=backoff_multiplier, + **kwargs, + ) ) def _request_handler( - request: 'Request', + request: 'Request', ) -> 'Tuple[asyncio.Future, Optional[asyncio.Future]]': """ For HTTP Client, for each request in the iterator, we `send_message` using diff --git a/jina/clients/base/websocket.py b/jina/clients/base/websocket.py index 49323e2d1a351..70893ab945d52 100644 --- a/jina/clients/base/websocket.py +++ b/jina/clients/base/websocket.py @@ -33,7 +33,12 @@ async def _is_flow_ready(self, **kwargs) -> bool: proto = 'wss' if self.args.tls else 'ws' url = f'{proto}://{self.args.host}:{self.args.port}/dry_run' iolet = await stack.enter_async_context( - WebsocketClientlet(url=url, logger=self.logger, **kwargs) + WebsocketClientlet( + url=url, + logger=self.logger, + tracer_provider=self.tracer_provider, + **kwargs, + ) ) async def _receive(): @@ -110,8 +115,16 @@ async def _get_results( proto = 'wss' if self.args.tls else 'ws' url = f'{proto}://{self.args.host}:{self.args.port}/' iolet = await stack.enter_async_context( - WebsocketClientlet(url=url, logger=self.logger, max_attempts=max_attempts, initial_backoff=initial_backoff, - max_backoff=max_backoff, backoff_multiplier=backoff_multiplier, **kwargs) + WebsocketClientlet( + url=url, + logger=self.logger, + tracer_provider=self.tracer_provider, + max_attempts=max_attempts, + initial_backoff=initial_backoff, + max_backoff=max_backoff, + backoff_multiplier=backoff_multiplier, + **kwargs, + ) ) request_buffer: Dict[ diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 36f637c5227aa..e6baba23edfe9 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -17,7 +17,6 @@ from jina.logging.logger import JinaLogger from jina.serve.executors.decorators import ( avoid_concurrent_lock_cls, - requests, store_init_kwargs, wrap_func, ) @@ -136,7 +135,7 @@ def __init__( self._add_requests(requests) self._add_runtime_args(runtime_args) self._init_monitoring() - self._init_instrumentation() + self._init_instrumentation(runtime_args) self._init_workspace = workspace self.logger = JinaLogger(self.__class__.__name__) if __dry_run_endpoint__ not in self.requests: @@ -182,13 +181,21 @@ def _init_monitoring(self): self._summary_method = None self._metrics_buffer = None - def _init_instrumentation(self): - name = self.__class__.__name__ - if hasattr(self.runtime_args, 'name'): - name = self.runtime_args.name + def _init_instrumentation(self, _runtime_args: Optional[Dict]): + instrumentating_module_name = ( + _runtime_args.name + if hasattr(_runtime_args, 'name') + else self.__class__.__name__ + ) - self.tracer = trace.get_tracer(name) - self.meter = metrics.get_meter(name) + self.tracer_provider = _runtime_args.get( + 'tracer_provider', trace.NoOpTracerProvider() + ) + self.tracer = self.tracer_provider.get_tracer(instrumentating_module_name) + self.meter_provider = _runtime_args.get( + 'meter_provider', metrics.NoOpMeterProvider() + ) + self.meter = self.meter_provider.get_meter(instrumentating_module_name) def _add_requests(self, _requests: Optional[Dict]): if not hasattr(self, 'requests'): diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index 7c83cb6ac4c88..5f533ad6593a0 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -1,6 +1,8 @@ import abc import argparse -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Optional, Sequence + +from opentelemetry import metrics, trace from jina.jaml import JAMLCompatible from jina.logging.logger import JinaLogger @@ -40,6 +42,8 @@ def set_streamer( timeout_send: Optional[float] = None, metrics_registry: Optional['CollectorRegistry'] = None, runtime_name: Optional[str] = None, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + tracing_client_interceptor: Optional[Any] = None, ): """ Set streamer object by providing runtime parameters. @@ -47,6 +51,8 @@ def set_streamer( :param timeout_send: grpc connection timeout :param metrics_registry: metric registry when monitoring is enabled :param runtime_name: name of the runtime providing the streamer + :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests if asycnio is True + :param tracing_client_interceptor: A gprc client tracing interceptor for tracing requests if asyncio is False """ import json @@ -69,6 +75,8 @@ def set_streamer( prefetch=args.prefetch, logger=self.logger, metrics_registry=metrics_registry, + aio_tracing_client_interceptors=aio_tracing_client_interceptors, + tracing_client_interceptor=tracing_client_interceptor, ) @abc.abstractmethod diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 3e17deabbb7d4..761175f3a2761 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,3 +1,5 @@ +from typing import Optional + from opentelemetry import metrics, trace from opentelemetry.instrumentation.grpc import ( client_interceptor as grpc_client_interceptor, @@ -14,12 +16,21 @@ class InstrumentationMixin: '''Instrumentation mixin for OpenTelemetery Tracing and Metrics handling''' - def _setup_instrumentation(self) -> None: - name = self.__class__.__name__ - if hasattr(self, 'name') and self.name: - name = self.name + def _setup_instrumentation( + self, + name: str, + opentelemetry_tracing: Optional[bool] = False, + span_exporter_host: Optional[str] = '0.0.0.0', + span_exporter_port: Optional[int] = 6831, + opentelemetry_metrics: Optional[bool] = False, + metrics_exporter_host: Optional[str] = '0.0.0.0', + metrics_exporter_port: Optional[int] = 6831, + ) -> None: + + self.opentelemetry_tracing = opentelemetry_tracing + self.opentelemetry_metrics = opentelemetry_metrics - if self.args.opentelemetry_tracing: + if opentelemetry_tracing: from opentelemetry.exporter.jaeger.thrift import JaegerExporter from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider @@ -29,8 +40,8 @@ def _setup_instrumentation(self) -> None: provider = TracerProvider(resource=resource) processor = BatchSpanProcessor( JaegerExporter( - agent_host_name=self.args.span_exporter_host, - agent_port=self.args.span_exporter_port, + agent_host_name=span_exporter_host, + agent_port=span_exporter_port, ) ) provider.add_span_processor(processor) @@ -40,7 +51,7 @@ def _setup_instrumentation(self) -> None: self.tracer_provider = trace.NoOpTracerProvider() self.tracer = trace.NoOpTracer() - if self.args.opentelemetry_metrics: + if opentelemetry_metrics: from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import ( ConsoleMetricExporter, @@ -54,11 +65,11 @@ def _setup_instrumentation(self) -> None: meter_provider = MeterProvider( metric_readers=[metric_reader], resource=resource ) - self.metrics_provider = meter_provider - self.meter = self.metrics_provider.get_meter(name) + self.meter_provider = meter_provider + self.meter = self.meter_provider.get_meter(name) else: - self.metrics_provider = metrics.NoOpMeterProvider() - self.meter = metrics.NoOpMeter() + self.meter_provider = metrics.NoOpMeterProvider() + self.meter = metrics.NoOpMeter(name='no-op') def aio_tracing_server_interceptor(self): '''Create a gRPC aio server interceptor. @@ -71,11 +82,15 @@ def aio_tracing_server_interceptor(self): return OpenTelemetryAioServerInterceptor(self.tracer) @staticmethod - def aio_tracing_client_interceptors(): + def aio_tracing_client_interceptors( + tracer: Optional[trace.Tracer] = trace.NoOpTracer(), + ): '''Create a gRPC client aio channel interceptor. + :param tracer: Optional tracer that is used to instrument the client interceptors. If absent, a NoOpTracer will be used. :returns: An invocation-side list of aio interceptor objects. ''' - tracer = trace.get_tracer(__name__) + if not tracer: + tracer = trace.NoOpTracer() return [ UnaryUnaryAioClientInterceptor(tracer), @@ -85,8 +100,13 @@ def aio_tracing_client_interceptors(): ] @staticmethod - def tracing_client_interceptor(): + def tracing_client_interceptor( + tracer_provider: Optional[trace.TracerProvider] = trace.NoOpTracerProvider(), + ): ''' + :param tracer_provider: Optional tracer provider that is used to instrument the client interceptor. If absent, a NoOpTracer provider will be used. :returns: a gRPC client interceptor with the global tracing provider. ''' - return grpc_client_interceptor(trace.get_tracer_provider()) + if not tracer_provider: + tracer_provider = trace.NoOpTracerProvider() + return grpc_client_interceptor(tracer_provider) diff --git a/jina/serve/networking.py b/jina/serve/networking.py index b81e000504a05..173b8a63a5ef7 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -3,7 +3,7 @@ import os from collections import defaultdict from dataclasses import dataclass -from typing import Dict, List, Optional, Sequence, Set, Tuple, Union +from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union from urllib.parse import urlparse import grpc @@ -60,6 +60,8 @@ def __init__( metrics: _NetworkingMetrics, logger, runtine_name: str, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + tracing_client_interceptor: Optional[Any] = None, ): self.runtime_name = runtine_name self._connections = [] @@ -69,6 +71,8 @@ def __init__( self._metrics = metrics self._logger = logger self._destroyed_event = asyncio.Event() + self.aio_tracing_client_interceptors = aio_tracing_client_interceptors + self.tracing_client_interceptors = tracing_client_interceptor async def reset_connection(self, address: str) -> Union[grpc.aio.Channel, None]: """ @@ -113,7 +117,9 @@ def add_connection(self, address: str): """ if address not in self._address_to_connection_idx: self._address_to_connection_idx[address] = len(self._connections) - stubs, channel = self._create_connection(address) + stubs, channel = self._create_connection( + address, + ) self._address_to_channel[address] = channel self._connections.append(stubs) @@ -158,7 +164,10 @@ def _create_connection(self, address): use_tls = parsed_address.scheme in TLS_PROTOCOL_SCHEMES stubs, channel = GrpcConnectionPool.create_async_channel_stub( - address, metrics=self._metrics, tls=use_tls, enable_trace=True + address, + metrics=self._metrics, + tls=use_tls, + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors, ) return stubs, channel @@ -417,6 +426,8 @@ def __init__( runtime_name: str, logger: Optional[JinaLogger], metrics: _NetworkingMetrics, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + tracing_client_interceptor: Optional[Any] = None, ): self._logger = logger # this maps deployments to shards or heads @@ -428,6 +439,8 @@ def __init__( if os.name != 'nt': os.unsetenv('http_proxy') os.unsetenv('https_proxy') + self.aio_tracing_client_interceptors = aio_tracing_client_interceptors + self.tracing_client_interceptor = tracing_client_interceptor def add_replica(self, deployment: str, shard_id: int, address: str): self._add_connection(deployment, shard_id, address, 'shards') @@ -528,7 +541,11 @@ def _add_connection( self._add_deployment(deployment) if entity_id not in self._deployments[deployment][type]: connection_list = ReplicaList( - self._metrics, self._logger, self.runtime_name + self._metrics, + self._logger, + self.runtime_name, + self.aio_tracing_client_interceptors, + self.tracing_client_interceptor, ) self._deployments[deployment][type][entity_id] = connection_list @@ -576,6 +593,8 @@ def __init__( logger: Optional[JinaLogger] = None, compression: Optional[str] = None, metrics_registry: Optional['CollectorRegistry'] = None, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + tracing_client_interceptor: Optional[Any] = None, ): self._logger = logger or JinaLogger(self.__class__.__name__) @@ -626,8 +645,14 @@ def __init__( send_requests_bytes_metrics, ) + self.aio_tracing_client_interceptors = aio_tracing_client_interceptors + self.tracing_client_interceptor = tracing_client_interceptor self._connections = self._ConnectionPoolMap( - runtime_name, self._logger, self._metrics + runtime_name, + self._logger, + self._metrics, + self.aio_tracing_client_interceptors, + self.tracing_client_interceptor, ) self._deployment_address_map = {} @@ -1034,7 +1059,8 @@ def get_grpc_channel( asyncio: bool = False, tls: bool = False, root_certificates: Optional[str] = None, - enable_trace: Optional[bool] = True, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + tracing_client_interceptor: Optional[Any] = None, ) -> grpc.Channel: """ Creates a grpc channel to the given address @@ -1044,8 +1070,8 @@ def get_grpc_channel( :param asyncio: If True, use the asyncio implementation of the grpc channel :param tls: If True, use tls encryption for the grpc channel :param root_certificates: The path to the root certificates for tls, only used if tls is True - :param enable_trace: If True the tracing interceptors are added to the channel otherwise the channel will not be provided with any tracing interceptors. - + :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests if asycnio is True + :param tracing_client_interceptor: A gprc client tracing interceptor for tracing requests if asyncio is False :return: A grpc channel or an asyncio channel """ @@ -1059,20 +1085,12 @@ def get_grpc_channel( ) if asyncio: - interceptors = ( - InstrumentationMixin.aio_tracing_client_interceptors() - if enable_trace - else None - ) return GrpcConnectionPool.__aio_channel_with_tracing_interceptor( - address, credentials, options, interceptors + address, credentials, options, aio_tracing_client_interceptors ) - interceptor = ( - InstrumentationMixin.tracing_client_interceptor() if enable_trace else None - ) return GrpcConnectionPool.__channel_with_tracing_interceptor( - address, credentials, options, interceptor + address, credentials, options, tracing_client_interceptor ) @staticmethod @@ -1140,7 +1158,6 @@ def send_health_check_sync( target, tls=tls, root_certificates=root_certificates, - enable_trace=False, ) as channel: health_check_req = health_pb2.HealthCheckRequest() health_check_req.service = '' @@ -1252,7 +1269,7 @@ def create_async_channel_stub( metrics: _NetworkingMetrics, tls=False, root_certificates: Optional[str] = None, - enable_trace: Optional[bool] = False, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, ) -> Tuple[ConnectionStubs, grpc.aio.Channel]: """ Creates an async GRPC Channel. This channel has to be closed eventually! @@ -1261,7 +1278,7 @@ def create_async_channel_stub( :param tls: if True, use tls for the grpc channel :param root_certificates: the path to the root certificates for tls, only u :param metrics: NetworkingMetrics object that contain optional metrics - :param enable_trace: If True the tracing interceptors are added to the channel otherwise the channel will not be provided with any tracing interceptors. + :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests for asycnio channel :returns: DataRequest stubs and an async grpc channel """ channel = GrpcConnectionPool.get_grpc_channel( @@ -1269,7 +1286,7 @@ def create_async_channel_stub( asyncio=True, tls=tls, root_certificates=root_certificates, - enable_trace=enable_trace, + aio_tracing_client_interceptors=aio_tracing_client_interceptors, ) return ( diff --git a/jina/serve/runtimes/asyncio.py b/jina/serve/runtimes/asyncio.py index caf733abf2889..0436c83dc4a2f 100644 --- a/jina/serve/runtimes/asyncio.py +++ b/jina/serve/runtimes/asyncio.py @@ -66,7 +66,15 @@ def __init__( ) self._setup_monitoring() - self._setup_instrumentation() + self._setup_instrumentation( + name=self.args.name, + opentelemetry_tracing=self.args.opentelemetry_tracing, + span_exporter_host=self.args.span_exporter_host, + span_exporter_port=self.args.span_exporter_port, + opentelemetry_metrics=self.args.opentelemetry_metrics, + metrics_exporter_host=self.args.metrics_exporter_host, + metrics_exporter_port=self.args.metrics_exporter_port, + ) send_telemetry_event(event='start', obj=self, entity_id=self._entity_id) self._start_time = time.time() self._loop.run_until_complete(self.async_setup()) diff --git a/jina/serve/runtimes/gateway/grpc/__init__.py b/jina/serve/runtimes/gateway/grpc/__init__.py index 644cc624ec514..191a542f3711c 100644 --- a/jina/serve/runtimes/gateway/grpc/__init__.py +++ b/jina/serve/runtimes/gateway/grpc/__init__.py @@ -28,6 +28,7 @@ async def async_setup(self): self.gateway = GRPCGateway( name=self.name, grpc_server_options=self.args.grpc_server_options, + grpc_tracing_server_interceptors=[self.aio_tracing_server_interceptor()], port=self.args.port, ssl_keyfile=self.args.ssl_keyfile, ssl_certfile=self.args.ssl_certfile, @@ -38,6 +39,12 @@ async def async_setup(self): timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.name, + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( + self.tracer + ), + tracing_client_interceptor=self.tracing_client_interceptor( + self.tracer_provider + ), ) await self.gateway.setup_server() diff --git a/jina/serve/runtimes/gateway/grpc/gateway.py b/jina/serve/runtimes/gateway/grpc/gateway.py index 22013691e20b4..5190399cfab80 100644 --- a/jina/serve/runtimes/gateway/grpc/gateway.py +++ b/jina/serve/runtimes/gateway/grpc/gateway.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Any, Optional, Sequence import grpc from grpc_health.v1 import health, health_pb2, health_pb2_grpc @@ -20,6 +20,7 @@ def __init__( self, port: Optional[int] = None, grpc_server_options: Optional[dict] = None, + grpc_tracing_server_interceptors: Optional[Sequence[Any]] = None, ssl_keyfile: Optional[str] = None, ssl_certfile: Optional[str] = None, **kwargs, @@ -27,6 +28,7 @@ def __init__( """Initialize the gateway :param port: The port of the Gateway, which the client should connect to. :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} + :param grpc_tracing_server_interceptors: Optional list of aio grpc tracing server interceptors. :param ssl_keyfile: the path to the key file :param ssl_certfile: the path to the certificate file :param kwargs: keyword args @@ -37,7 +39,8 @@ def __init__( self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.server = grpc.aio.server( - options=_get_grpc_server_options(self.grpc_server_options) + options=_get_grpc_server_options(self.grpc_server_options), + interceptors=grpc_tracing_server_interceptors, ) self.health_servicer = health.HealthServicer(experimental_non_blocking=True) diff --git a/jina/serve/runtimes/gateway/http/__init__.py b/jina/serve/runtimes/gateway/http/__init__.py index 0cb2903194f9d..55d661713ab75 100644 --- a/jina/serve/runtimes/gateway/http/__init__.py +++ b/jina/serve/runtimes/gateway/http/__init__.py @@ -32,6 +32,8 @@ async def async_setup(self): ssl_keyfile=self.args.ssl_keyfile, ssl_certfile=self.args.ssl_certfile, uvicorn_kwargs=self.args.uvicorn_kwargs, + opentelemetry_tracing=self.opentelemetry_tracing, + tracer_provider=self.tracer_provider, ) self.gateway.set_streamer( @@ -39,6 +41,12 @@ async def async_setup(self): timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.args.name, + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( + self.tracer + ), + tracing_client_interceptor=self.tracing_client_interceptor( + self.tracer_provider + ), ) await self.gateway.setup_server() diff --git a/jina/serve/runtimes/gateway/http/app.py b/jina/serve/runtimes/gateway/http/app.py index 2ff5f9175d714..71d1092005078 100644 --- a/jina/serve/runtimes/gateway/http/app.py +++ b/jina/serve/runtimes/gateway/http/app.py @@ -2,6 +2,8 @@ import json from typing import TYPE_CHECKING, Dict, List, Optional +from opentelemetry import trace + from jina import __version__ from jina.clients.request import request_generator from jina.enums import DataInputType @@ -24,6 +26,8 @@ def get_fastapi_app( expose_graphql_endpoint: bool, cors: bool, logger: 'JinaLogger', + opentelemetry_tracing: Optional[bool] = None, + tracer_provider: Optional[trace.TracerProvider] = None, ): """ Get the app from FastAPI as the REST interface. @@ -39,6 +43,8 @@ def get_fastapi_app( :param expose_graphql_endpoint: If set, /graphql endpoint is added to HTTP interface. :param cors: If set, a CORS middleware is added to FastAPI frontend to allow cross-origin access. :param logger: Jina logger. + :param opentelemetry_tracing: Enables tracing is set to True. + :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :return: fastapi app """ with ImportExtensions(required=True): @@ -59,6 +65,11 @@ def get_fastapi_app( version=__version__, ) + if opentelemetry_tracing: + from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + + FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider) + if cors: app.add_middleware( CORSMiddleware, diff --git a/jina/serve/runtimes/gateway/http/gateway.py b/jina/serve/runtimes/gateway/http/gateway.py index 1d6ab534af742..71e086df5c872 100644 --- a/jina/serve/runtimes/gateway/http/gateway.py +++ b/jina/serve/runtimes/gateway/http/gateway.py @@ -2,6 +2,8 @@ import os from typing import Optional +from opentelemetry import trace + from jina import __default_host__ from jina.importer import ImportExtensions @@ -25,6 +27,8 @@ def __init__( ssl_keyfile: Optional[str] = None, ssl_certfile: Optional[str] = None, uvicorn_kwargs: Optional[dict] = None, + opentelemetry_tracing: Optional[bool] = None, + tracer_provider: Optional[trace.TracerProvider] = None, **kwargs ): """Initialize the gateway @@ -42,6 +46,8 @@ def __init__( :param ssl_keyfile: the path to the key file :param ssl_certfile: the path to the certificate file :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server + :param opentelemetry_tracing: Enables tracing if set to True. + :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :param kwargs: keyword args """ super().__init__(**kwargs) @@ -56,6 +62,8 @@ def __init__( self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.uvicorn_kwargs = uvicorn_kwargs + self.opentelemetery_tracing = opentelemetry_tracing + self.tracer_provider = tracer_provider async def setup_server(self): """ @@ -74,6 +82,8 @@ async def setup_server(self): expose_graphql_endpoint=self.expose_graphql_endpoint, cors=self.cors, logger=self.logger, + opentelemetry_tracing=self.opentelemetery_tracing, + tracer_provider=self.tracer_provider, ) ) diff --git a/jina/serve/runtimes/gateway/websocket/__init__.py b/jina/serve/runtimes/gateway/websocket/__init__.py index 3f132f73c164a..e3df72d6e1f71 100644 --- a/jina/serve/runtimes/gateway/websocket/__init__.py +++ b/jina/serve/runtimes/gateway/websocket/__init__.py @@ -25,6 +25,8 @@ async def async_setup(self): ssl_certfile=self.args.ssl_certfile, uvicorn_kwargs=self.args.uvicorn_kwargs, logger=self.logger, + opentelemetry_tracing=self.opentelemetry_tracing, + tracer_provider=self.tracer_provider, ) self.gateway.set_streamer( @@ -32,6 +34,12 @@ async def async_setup(self): timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.args.name, + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( + self.tracer + ), + tracing_client_interceptor=self.tracing_client_interceptor( + self.tracer_provider + ), ) await self.gateway.setup_server() diff --git a/jina/serve/runtimes/gateway/websocket/app.py b/jina/serve/runtimes/gateway/websocket/app.py index 2793e18898b51..ee859f4291a45 100644 --- a/jina/serve/runtimes/gateway/websocket/app.py +++ b/jina/serve/runtimes/gateway/websocket/app.py @@ -1,6 +1,8 @@ import argparse from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Union +from opentelemetry import trace + from jina.clients.request import request_generator from jina.enums import DataInputType, WebsocketSubProtocols from jina.excepts import InternalNetworkError @@ -24,12 +26,16 @@ def _fits_ws_close_msg(msg: str): def get_fastapi_app( streamer: 'GatewayStreamer', logger: 'JinaLogger', + opentelemetry_tracing: Optional[bool] = None, + tracer_provider: Optional[trace.TracerProvider] = None, ): """ Get the app from FastAPI as the Websocket interface. :param streamer: gateway streamer object. :param logger: Jina logger. + :param opentelemetry_tracing: Enables tracing is set to True. + :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :return: fastapi app """ @@ -109,6 +115,11 @@ async def send( app = FastAPI() + if opentelemetry_tracing: + from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + + FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider) + @app.get( path='/', summary='Get the health of Jina service', diff --git a/jina/serve/runtimes/gateway/websocket/gateway.py b/jina/serve/runtimes/gateway/websocket/gateway.py index 7295adf486aa0..77ce7a3503bb2 100644 --- a/jina/serve/runtimes/gateway/websocket/gateway.py +++ b/jina/serve/runtimes/gateway/websocket/gateway.py @@ -2,6 +2,8 @@ import os from typing import Optional +from opentelemetry import trace + from jina import __default_host__ from jina.importer import ImportExtensions @@ -18,6 +20,8 @@ def __init__( ssl_keyfile: Optional[str] = None, ssl_certfile: Optional[str] = None, uvicorn_kwargs: Optional[dict] = None, + opentelemetry_tracing: Optional[bool] = None, + tracer_provider: Optional[trace.TracerProvider] = None, **kwargs ): """Initialize the gateway @@ -25,6 +29,8 @@ def __init__( :param ssl_keyfile: the path to the key file :param ssl_certfile: the path to the certificate file :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server + :param opentelemetry_tracing: Enables tracing if set to True. + :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :param kwargs: keyword args """ super().__init__(**kwargs) @@ -32,6 +38,8 @@ def __init__( self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.uvicorn_kwargs = uvicorn_kwargs + self.opentelemetery_tracing = opentelemetry_tracing + self.tracer_provider = tracer_provider async def setup_server(self): """ @@ -43,6 +51,8 @@ async def setup_server(self): get_fastapi_app( streamer=self.streamer, logger=self.logger, + opentelemetry_tracing=self.opentelemetery_tracing, + tracer_provider=self.tracer_provider, ) ) diff --git a/jina/serve/runtimes/request_handlers/data_request_handler.py b/jina/serve/runtimes/request_handlers/data_request_handler.py index 4bc0603638615..1a65e7f554e70 100644 --- a/jina/serve/runtimes/request_handlers/data_request_handler.py +++ b/jina/serve/runtimes/request_handlers/data_request_handler.py @@ -1,7 +1,7 @@ -import copy -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional from docarray import DocumentArray +from opentelemetry import metrics, trace from opentelemetry.context.context import Context from jina import __default_endpoint__ @@ -28,6 +28,8 @@ def __init__( args: 'argparse.Namespace', logger: 'JinaLogger', metrics_registry: Optional['CollectorRegistry'] = None, + tracer_provider: Optional[trace.TracerProvider] = None, + meter_provider: Optional[metrics.MeterProvider] = None, **kwargs, ): """Initialize private parameters and execute private loading functions. @@ -35,6 +37,8 @@ def __init__( :param args: args from CLI :param logger: the logger provided by the user :param metrics_registry: optional metrics registry for prometheus used if we need to expose metrics from the executor of from the data request handler + :param tracer_provider: Optional tracer_provider that will be provided to the executor for tracing + :param meter_provider: Optional meter_provider that will be provided to the executor for metrics :param kwargs: extra keyword arguments """ super().__init__() @@ -42,7 +46,11 @@ def __init__( self.args.parallel = self.args.shards self.logger = logger self._is_closed = False - self._load_executor(metrics_registry) + self._load_executor( + metrics_registry=metrics_registry, + tracer_provider=tracer_provider, + meter_provider=meter_provider, + ) self._init_monitoring(metrics_registry) def _init_monitoring(self, metrics_registry: Optional['CollectorRegistry'] = None): @@ -86,10 +94,17 @@ def _init_monitoring(self, metrics_registry: Optional['CollectorRegistry'] = Non self._request_size_metrics = None self._sent_response_size_metrics = None - def _load_executor(self, metrics_registry: Optional['CollectorRegistry'] = None): + def _load_executor( + self, + metrics_registry: Optional['CollectorRegistry'] = None, + tracer_provider: Optional[trace.TracerProvider] = None, + meter_provider: Optional[metrics.MeterProvider] = None, + ): """ Load the executor to this runtime, specified by ``uses`` CLI argument. :param metrics_registry: Optional prometheus metrics registry that will be passed to the executor so that it can expose metrics + :param tracer_provider: Optional tracer_provider that will be provided to the executor for tracing + :param meter_provider: Optional meter_provider that will be provided to the executor for metrics """ try: self._executor: BaseExecutor = BaseExecutor.load_config( @@ -104,6 +119,8 @@ def _load_executor(self, metrics_registry: Optional['CollectorRegistry'] = None) 'replicas': self.args.replicas, 'name': self.args.name, 'metrics_registry': metrics_registry, + 'tracer_provider': tracer_provider, + 'meter_provider': meter_provider, }, py_modules=self.args.py_modules, extra_search_paths=self.args.extra_search_paths, diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index 6e8f45f4ce5d9..a2384df634de5 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -80,7 +80,11 @@ async def async_setup(self): # otherwise readiness check is not valid # The DataRequestHandler needs to be started BEFORE the grpc server self._data_request_handler = DataRequestHandler( - self.args, self.logger, self.metrics_registry + self.args, + self.logger, + self.metrics_registry, + self.tracer_provider, + self.meter_provider, ) await self._async_setup_grpc_server() diff --git a/jina/serve/streamer.py b/jina/serve/streamer.py index b1cc562bc76a6..4b0f34b4a1d9f 100644 --- a/jina/serve/streamer.py +++ b/jina/serve/streamer.py @@ -1,8 +1,10 @@ -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union from docarray import DocumentArray +from opentelemetry import metrics, trace from jina.logging.logger import JinaLogger +from jina.serve.instrumentation import InstrumentationMixin from jina.serve.networking import GrpcConnectionPool from jina.serve.runtimes.gateway.graph.topology_graph import TopologyGraph from jina.serve.runtimes.gateway.request_handling import RequestHandler @@ -32,6 +34,8 @@ def __init__( prefetch: int = 0, logger: Optional['JinaLogger'] = None, metrics_registry: Optional['CollectorRegistry'] = None, + aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + tracing_client_interceptor: Optional[Any] = None, ): """ :param graph_representation: A dictionary describing the topology of the Deployments. 2 special nodes are expected, the name `start-gateway` and `end-gateway` to @@ -47,6 +51,8 @@ def __init__( :param prefetch: How many Requests are processed from the Client at the same time. :param logger: Optional logger that can be used for logging :param metrics_registry: optional metrics registry for prometheus used if we need to expose metrics + :param aio_tracing_client_interceptors: Optional list of aio grpc tracing server interceptors. + :param tracing_client_interceptor: Optional gprc tracing server interceptor. """ topology_graph = self._create_topology_graph( graph_representation, @@ -58,7 +64,12 @@ def __init__( self.runtime_name = runtime_name self._connection_pool = self._create_connection_pool( - executor_addresses, compression, metrics_registry, logger + executor_addresses, + compression, + metrics_registry, + logger, + aio_tracing_client_interceptors, + tracing_client_interceptor, ) request_handler = RequestHandler(metrics_registry, runtime_name) @@ -90,7 +101,13 @@ def _create_topology_graph( ) def _create_connection_pool( - self, deployments_addresses, compression, metrics_registry, logger + self, + deployments_addresses, + compression, + metrics_registry, + logger, + aio_tracing_client_interceptors, + tracing_client_interceptor, ): # add the connections needed connection_pool = GrpcConnectionPool( @@ -98,6 +115,8 @@ def _create_connection_pool( logger=logger, compression=compression, metrics_registry=metrics_registry, + aio_tracing_client_interceptors=aio_tracing_client_interceptors, + tracing_client_interceptor=tracing_client_interceptor, ) for deployment_name, addresses in deployments_addresses.items(): for address in addresses: From 175a399516faeb250f02b1763b75b430d707ca3f Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Fri, 30 Sep 2022 13:13:52 +0000 Subject: [PATCH 41/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 3839d3e936db2..1a91fbdb88ac9 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -211,6 +211,7 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], + 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -223,6 +224,7 @@ '--version', '--loglevel', 'login', + 'logout', 'deploy', 'list', 'logs', From 030b980bc7ad3694a1e9477bda6ba8f6db124872 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 30 Sep 2022 15:23:56 +0200 Subject: [PATCH 42/79] feat(instrumentation): configure a OTLP collector for exporting traces and metrics --- extra-requirements.txt | 2 +- jina/serve/instrumentation/__init__.py | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/extra-requirements.txt b/extra-requirements.txt index eb5d2da9649cc..c65e75b21485b 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -45,7 +45,7 @@ opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel -opentelemetry-exporter-jaeger>=1.12.0: perf,standrad,devel +opentelemetry-exporter-otlp-proto-grpc>=1.13.0: perf,standrad,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 761175f3a2761..f8e4e629a6c90 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -31,7 +31,9 @@ def _setup_instrumentation( self.opentelemetry_metrics = opentelemetry_metrics if opentelemetry_tracing: - from opentelemetry.exporter.jaeger.thrift import JaegerExporter + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( + OTLPSpanExporter, + ) from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor @@ -39,9 +41,8 @@ def _setup_instrumentation( resource = Resource(attributes={SERVICE_NAME: name}) provider = TracerProvider(resource=resource) processor = BatchSpanProcessor( - JaegerExporter( - agent_host_name=span_exporter_host, - agent_port=span_exporter_port, + OTLPSpanExporter( + endpoint=f'{span_exporter_host}:{span_exporter_port}', insecure=True ) ) provider.add_span_processor(processor) @@ -52,16 +53,21 @@ def _setup_instrumentation( self.tracer = trace.NoOpTracer() if opentelemetry_metrics: - from opentelemetry.sdk.metrics import MeterProvider - from opentelemetry.sdk.metrics.export import ( - ConsoleMetricExporter, - PeriodicExportingMetricReader, + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + OTLPMetricExporter, ) + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.sdk.resources import SERVICE_NAME, Resource resource = Resource(attributes={SERVICE_NAME: name}) - metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter()) + metric_reader = PeriodicExportingMetricReader( + OTLPMetricExporter( + endpoint=f'{metrics_exporter_host}:{metrics_exporter_port}', + insecure=True, + ) + ) meter_provider = MeterProvider( metric_readers=[metric_reader], resource=resource ) From c6864980e6a20d50d8e605bbb7f718140b701e4a Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Fri, 30 Sep 2022 13:25:11 +0000 Subject: [PATCH 43/79] style: fix overload and cli autocomplete --- jina/resources/extra-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jina/resources/extra-requirements.txt b/jina/resources/extra-requirements.txt index eb5d2da9649cc..c65e75b21485b 100644 --- a/jina/resources/extra-requirements.txt +++ b/jina/resources/extra-requirements.txt @@ -45,7 +45,7 @@ opentelemetry-exporter-prometheus>=1.12.0rc1: perf,standard,devel opentelemetry-semantic-conventions>=0.33b0: perf,standard,devel opentelemetry-instrumentation-aiohttp-client>=0.33b0: perf,standard,devel opentelemetry-instrumentation-fastapi>=0.33b0: perf,standard,devel -opentelemetry-exporter-jaeger>=1.12.0: perf,standrad,devel +opentelemetry-exporter-otlp-proto-grpc>=1.13.0: perf,standrad,devel fastapi>=0.76.0: standard,devel uvicorn[standard]: standard,devel docarray[common]>=0.16.3: standard,devel From 6d21a3a1ea2b050d445deae32059acae080f0eb5 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 4 Oct 2022 13:45:20 +0200 Subject: [PATCH 44/79] feat(instrumentation): return None for aio server interceptors if tracing is disabled --- jina/serve/instrumentation/__init__.py | 11 +++++++---- jina/serve/runtimes/gateway/grpc/__init__.py | 2 +- jina/serve/runtimes/head/__init__.py | 2 +- jina/serve/runtimes/worker/__init__.py | 2 +- jina/serve/streamer.py | 2 ++ 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index f8e4e629a6c90..4695755d5a402 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -81,11 +81,14 @@ def aio_tracing_server_interceptor(self): '''Create a gRPC aio server interceptor. :returns: A service-side aio interceptor object. ''' - from jina.serve.instrumentation._aio_server import ( - OpenTelemetryAioServerInterceptor, - ) + if self.opentelemetry_tracing: + from jina.serve.instrumentation._aio_server import ( + OpenTelemetryAioServerInterceptor, + ) - return OpenTelemetryAioServerInterceptor(self.tracer) + return [OpenTelemetryAioServerInterceptor(self.tracer)] + else: + return None @staticmethod def aio_tracing_client_interceptors( diff --git a/jina/serve/runtimes/gateway/grpc/__init__.py b/jina/serve/runtimes/gateway/grpc/__init__.py index 191a542f3711c..76adbc26f89a0 100644 --- a/jina/serve/runtimes/gateway/grpc/__init__.py +++ b/jina/serve/runtimes/gateway/grpc/__init__.py @@ -28,7 +28,7 @@ async def async_setup(self): self.gateway = GRPCGateway( name=self.name, grpc_server_options=self.args.grpc_server_options, - grpc_tracing_server_interceptors=[self.aio_tracing_server_interceptor()], + grpc_tracing_server_interceptors=self.aio_tracing_server_interceptor(), port=self.args.port, ssl_keyfile=self.args.ssl_keyfile, ssl_certfile=self.args.ssl_certfile, diff --git a/jina/serve/runtimes/head/__init__.py b/jina/serve/runtimes/head/__init__.py index 0a108c265bf8d..68709360120d0 100644 --- a/jina/serve/runtimes/head/__init__.py +++ b/jina/serve/runtimes/head/__init__.py @@ -146,7 +146,7 @@ async def async_setup(self): """Wait for the GRPC server to start""" self._grpc_server = grpc.aio.server( options=_get_grpc_server_options(self.args.grpc_server_options), - interceptors=[self.aio_tracing_server_interceptor()], + interceptors=self.aio_tracing_server_interceptor(), ) jina_pb2_grpc.add_JinaSingleDataRequestRPCServicer_to_server( diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index a2384df634de5..42d8c0afa3368 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -95,7 +95,7 @@ async def _async_setup_grpc_server(self): self._grpc_server = grpc.aio.server( options=_get_grpc_server_options(self.args.grpc_server_options), - interceptors=[self.aio_tracing_server_interceptor()], + interceptors=self.aio_tracing_server_interceptor(), ) jina_pb2_grpc.add_JinaSingleDataRequestRPCServicer_to_server( diff --git a/jina/serve/streamer.py b/jina/serve/streamer.py index 4b0f34b4a1d9f..c3bd849251c74 100644 --- a/jina/serve/streamer.py +++ b/jina/serve/streamer.py @@ -62,6 +62,8 @@ def __init__( retries, ) self.runtime_name = runtime_name + self.aio_tracing_client_interceptors = aio_tracing_client_interceptors + self.tracing_client_interceptor = tracing_client_interceptor self._connection_pool = self._create_connection_pool( executor_addresses, From 00c6c12957338f607e2dfaae5d589882ea77086f Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 5 Oct 2022 09:48:24 +0200 Subject: [PATCH 45/79] test: fix handling of optional args --- jina/serve/executors/__init__.py | 13 +++++++++---- .../test_clients_post_extra_kwargs.py | 7 +++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index e6baba23edfe9..eae6fcc9fffd5 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -188,12 +188,17 @@ def _init_instrumentation(self, _runtime_args: Optional[Dict]): else self.__class__.__name__ ) - self.tracer_provider = _runtime_args.get( - 'tracer_provider', trace.NoOpTracerProvider() + self.tracer_provider = ( + _runtime_args['tracer_provider'] + if hasattr(_runtime_args, 'tracer_provider') + else trace.NoOpTracerProvider() ) + self.tracer = self.tracer_provider.get_tracer(instrumentating_module_name) - self.meter_provider = _runtime_args.get( - 'meter_provider', metrics.NoOpMeterProvider() + self.meter_provider = ( + _runtime_args['meter_provider'] + if hasattr(_runtime_args, 'meter_provider') + else metrics.NoOpMeterProvider() ) self.meter = self.meter_provider.get_meter(instrumentating_module_name) diff --git a/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py b/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py index 0459a7602e5ad..5ac6376c83ffc 100644 --- a/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py +++ b/tests/integration/clients_extra_kwargs/test_clients_post_extra_kwargs.py @@ -40,10 +40,8 @@ async def intercept_service(self, continuation, handler_call_details): return self._deny class AlternativeGRPCGateway(GRPCGateway): - def __init__(self, namespace_args, *args, **kwargs): - super(AlternativeGRPCGateway, self).__init__( - args=namespace_args, *args, **kwargs - ) + def __init__(self, *args, **kwargs): + super(AlternativeGRPCGateway, self).__init__(*args, **kwargs) self.server = grpc.aio.server( interceptors=(AuthInterceptor('access_key'),), options=_get_grpc_server_options(self.grpc_server_options), @@ -72,6 +70,7 @@ async def async_setup(self): ) self.gateway.set_streamer( + args=self.args, timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.name, From 6e278291a27454796897b0d2b0a9c5d6e53f18b2 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 5 Oct 2022 10:42:45 +0200 Subject: [PATCH 46/79] fix: remove print debug statement --- jina/serve/runtimes/gateway/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 729b48767e705..6dc983881e290 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -53,7 +53,6 @@ async def async_setup(self): raise PortAlreadyUsed(f'port:{self.args.port}') uses_with = self.args.uses_with or {} - print(f'--->args: {self.args}') self.gateway = BaseGateway.load_config( self.args.uses, uses_with=dict( From 366a20ec1ad6884f60d6ca2faff1ee2b15e24ec2 Mon Sep 17 00:00:00 2001 From: Alaeddine Abdessalem Date: Wed, 5 Oct 2022 10:13:29 +0100 Subject: [PATCH 47/79] fix: fix gateway class loading --- jina/serve/runtimes/gateway/__init__.py | 2 ++ jina/serve/runtimes/gateway/grpc/__init__.py | 2 ++ jina/serve/runtimes/gateway/http/__init__.py | 2 ++ jina/serve/runtimes/gateway/websocket/__init__.py | 8 ++------ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 729b48767e705..421388bfaf04f 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -13,6 +13,8 @@ from jina.parsers.helper import _set_gateway_uses from jina.serve.gateway import BaseGateway from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime +from jina.serve.runtimes.gateway.grpc import GRPCGateway +from jina.serve.runtimes.gateway.http import HTTPGateway if TYPE_CHECKING: import multiprocessing diff --git a/jina/serve/runtimes/gateway/grpc/__init__.py b/jina/serve/runtimes/gateway/grpc/__init__.py index 5301ab6305b22..214829005da5e 100644 --- a/jina/serve/runtimes/gateway/grpc/__init__.py +++ b/jina/serve/runtimes/gateway/grpc/__init__.py @@ -1 +1,3 @@ from jina.serve.runtimes.gateway.grpc.gateway import GRPCGateway + +__all__ = ['GRPCGateway'] diff --git a/jina/serve/runtimes/gateway/http/__init__.py b/jina/serve/runtimes/gateway/http/__init__.py index a7924cb8adc42..2155fc5eab1d4 100644 --- a/jina/serve/runtimes/gateway/http/__init__.py +++ b/jina/serve/runtimes/gateway/http/__init__.py @@ -1 +1,3 @@ from .gateway import HTTPGateway + +__all__ = ['HTTPGateway'] diff --git a/jina/serve/runtimes/gateway/websocket/__init__.py b/jina/serve/runtimes/gateway/websocket/__init__.py index f14b4b53cbec0..3fb0a5abad1f6 100644 --- a/jina/serve/runtimes/gateway/websocket/__init__.py +++ b/jina/serve/runtimes/gateway/websocket/__init__.py @@ -1,8 +1,4 @@ -import asyncio - -from jina.serve.runtimes.gateway import GatewayRuntime from jina.serve.runtimes.gateway.websocket.app import get_fastapi_app - -__all__ = ['WebSocketGatewayRuntime'] - from jina.serve.runtimes.gateway.websocket.gateway import WebSocketGateway + +__all__ = ['WebSocketGateway'] From 963b82d143c3170e9007fc0a5b272031b0930d72 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 5 Oct 2022 13:35:06 +0200 Subject: [PATCH 48/79] feat(instrumentation): fix BaseGateway telemetry dependency injection --- jina/serve/gateway.py | 17 ++++++++++++++--- jina/serve/runtimes/gateway/__init__.py | 7 ++++--- jina/serve/runtimes/gateway/grpc/gateway.py | 11 +++++------ jina/serve/runtimes/gateway/http/gateway.py | 10 +--------- .../serve/runtimes/gateway/websocket/gateway.py | 10 +--------- 5 files changed, 25 insertions(+), 30 deletions(-) diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index 67e885a65036d..bf59b9b513e0c 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -4,6 +4,8 @@ import inspect from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence +from opentelemetry import trace + from jina.helper import convert_tuple_to_list from jina.jaml import JAMLCompatible from jina.logging.logger import JinaLogger @@ -72,12 +74,15 @@ def __init__( # TODO: original implementation also passes args, maybe move this to a setter/initializer func self.logger = JinaLogger(self.name) - def set_streamer( + def inject_dependencies( self, args: 'argparse.Namespace' = None, timeout_send: Optional[float] = None, metrics_registry: Optional['CollectorRegistry'] = None, runtime_name: Optional[str] = None, + opentelemetry_tracing: Optional[bool] = False, + tracer_provider: Optional[trace.TracerProvider] = None, + grpc_tracing_server_interceptors: Optional[Sequence[Any]] = None, aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, tracing_client_interceptor: Optional[Any] = None, ): @@ -87,9 +92,15 @@ def set_streamer( :param timeout_send: grpc connection timeout :param metrics_registry: metric registry when monitoring is enabled :param runtime_name: name of the runtime providing the streamer - :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests if asycnio is True - :param tracing_client_interceptor: A gprc client tracing interceptor for tracing requests if asyncio is False + :param opentelemetry_tracing: Enables tracing is set to True. + :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. + :param grpc_tracing_server_interceptors: List of async io gprc server tracing interceptors for tracing requests. + :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests if asycnio is True. + :param tracing_client_interceptor: A gprc client tracing interceptor for tracing requests if asyncio is False. """ + self.opentelemetry_tracing = opentelemetry_tracing + self.tracer_provider = tracer_provider + self.grpc_tracing_server_interceptors = grpc_tracing_server_interceptors import json from jina.serve.streamer import GatewayStreamer diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index fc3cfd5d7dd09..4ef2e81896b00 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -72,8 +72,6 @@ async def async_setup(self): ssl_certfile=self.args.ssl_certfile, uvicorn_kwargs=self.args.uvicorn_kwargs, proxy=self.args.proxy, - opentelemetry_tracing=self.opentelemetry_tracing, - tracer_provider=self.tracer_provider, **uses_with, ), uses_metas={}, @@ -84,11 +82,14 @@ async def async_setup(self): extra_search_paths=self.args.extra_search_paths, ) - self.gateway.set_streamer( + self.gateway.inject_dependencies( args=self.args, timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.args.name, + opentelemetry_tracing=self.opentelemetry_tracing, + tracer_provider=self.tracer_provider, + grpc_tracing_server_interceptors=self.aio_tracing_server_interceptor(), aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( self.tracer ), diff --git a/jina/serve/runtimes/gateway/grpc/gateway.py b/jina/serve/runtimes/gateway/grpc/gateway.py index 901e6cf9305ec..62dcba75e5faa 100644 --- a/jina/serve/runtimes/gateway/grpc/gateway.py +++ b/jina/serve/runtimes/gateway/grpc/gateway.py @@ -20,7 +20,6 @@ def __init__( self, port: Optional[int] = None, grpc_server_options: Optional[dict] = None, - grpc_tracing_server_interceptors: Optional[Sequence[Any]] = None, ssl_keyfile: Optional[str] = None, ssl_certfile: Optional[str] = None, **kwargs, @@ -28,7 +27,6 @@ def __init__( """Initialize the gateway :param port: The port of the Gateway, which the client should connect to. :param grpc_server_options: Dictionary of kwargs arguments that will be passed to the grpc server as options when starting the server, example : {'grpc.max_send_message_length': -1} - :param grpc_tracing_server_interceptors: Optional list of aio grpc tracing server interceptors. :param ssl_keyfile: the path to the key file :param ssl_certfile: the path to the certificate file :param kwargs: keyword args @@ -38,16 +36,17 @@ def __init__( self.grpc_server_options = grpc_server_options self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile - self.server = grpc.aio.server( - options=_get_grpc_server_options(self.grpc_server_options), - interceptors=grpc_tracing_server_interceptors, - ) self.health_servicer = health.HealthServicer(experimental_non_blocking=True) async def setup_server(self): """ setup GRPC server """ + self.server = grpc.aio.server( + options=_get_grpc_server_options(self.grpc_server_options), + interceptors=self.grpc_tracing_server_interceptors, + ) + jina_pb2_grpc.add_JinaRPCServicer_to_server( self.streamer._streamer, self.server ) diff --git a/jina/serve/runtimes/gateway/http/gateway.py b/jina/serve/runtimes/gateway/http/gateway.py index 4422ec46c88fe..aa6fad06f3069 100644 --- a/jina/serve/runtimes/gateway/http/gateway.py +++ b/jina/serve/runtimes/gateway/http/gateway.py @@ -2,8 +2,6 @@ import os from typing import Optional -from opentelemetry import trace - from jina import __default_host__ from jina.importer import ImportExtensions from jina.serve.gateway import BaseGateway @@ -27,8 +25,6 @@ def __init__( ssl_certfile: Optional[str] = None, uvicorn_kwargs: Optional[dict] = None, proxy: Optional[bool] = None, - opentelemetry_tracing: Optional[bool] = None, - tracer_provider: Optional[trace.TracerProvider] = None, **kwargs ): """Initialize the gateway @@ -48,8 +44,6 @@ def __init__( :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server :param proxy: If set, respect the http_proxy and https_proxy environment variables, otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy - :param opentelemetry_tracing: Enables tracing if set to True. - :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :param kwargs: keyword args """ super().__init__(**kwargs) @@ -64,8 +58,6 @@ def __init__( self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.uvicorn_kwargs = uvicorn_kwargs - self.opentelemetery_tracing = opentelemetry_tracing - self.tracer_provider = tracer_provider if not proxy and os.name != 'nt': os.unsetenv('http_proxy') @@ -88,7 +80,7 @@ async def setup_server(self): expose_graphql_endpoint=self.expose_graphql_endpoint, cors=self.cors, logger=self.logger, - opentelemetry_tracing=self.opentelemetery_tracing, + opentelemetry_tracing=self.opentelemetry_tracing, tracer_provider=self.tracer_provider, ) ) diff --git a/jina/serve/runtimes/gateway/websocket/gateway.py b/jina/serve/runtimes/gateway/websocket/gateway.py index 86598b552e175..4b3976770c6e1 100644 --- a/jina/serve/runtimes/gateway/websocket/gateway.py +++ b/jina/serve/runtimes/gateway/websocket/gateway.py @@ -2,8 +2,6 @@ import os from typing import Optional -from opentelemetry import trace - from jina import __default_host__ from jina.importer import ImportExtensions from jina.serve.gateway import BaseGateway @@ -20,8 +18,6 @@ def __init__( ssl_certfile: Optional[str] = None, uvicorn_kwargs: Optional[dict] = None, proxy: Optional[bool] = None, - opentelemetry_tracing: Optional[bool] = None, - tracer_provider: Optional[trace.TracerProvider] = None, **kwargs ): """Initialize the gateway @@ -31,8 +27,6 @@ def __init__( :param uvicorn_kwargs: Dictionary of kwargs arguments that will be passed to Uvicorn server when starting the server :param proxy: If set, respect the http_proxy and https_proxy environment variables, otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy - :param opentelemetry_tracing: Enables tracing if set to True. - :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :param kwargs: keyword args """ super().__init__(**kwargs) @@ -40,8 +34,6 @@ def __init__( self.ssl_keyfile = ssl_keyfile self.ssl_certfile = ssl_certfile self.uvicorn_kwargs = uvicorn_kwargs - self.opentelemetery_tracing = opentelemetry_tracing - self.tracer_provider = tracer_provider if not proxy and os.name != 'nt': os.unsetenv('http_proxy') @@ -57,7 +49,7 @@ async def setup_server(self): get_fastapi_app( streamer=self.streamer, logger=self.logger, - opentelemetry_tracing=self.opentelemetery_tracing, + opentelemetry_tracing=self.opentelemetry_tracing, tracer_provider=self.tracer_provider, ) ) From 6433930bd60d819d1ff2e534909b00c374aa52b6 Mon Sep 17 00:00:00 2001 From: Alaeddine Abdessalem Date: Wed, 5 Oct 2022 12:46:41 +0100 Subject: [PATCH 49/79] fix: fix WebsocketGateway loading --- jina/serve/runtimes/gateway/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 4ef2e81896b00..77fd9efcd8059 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -13,14 +13,18 @@ from jina.parsers.helper import _set_gateway_uses from jina.serve.gateway import BaseGateway from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime -from jina.serve.runtimes.gateway.grpc import GRPCGateway -from jina.serve.runtimes.gateway.http import HTTPGateway if TYPE_CHECKING: import multiprocessing import threading +# Keep these imports even if not used, since YAML parser needs to find them in imported modules +from jina.serve.runtimes.gateway.grpc import GRPCGateway +from jina.serve.runtimes.gateway.http import HTTPGateway +from jina.serve.runtimes.gateway.websocket import WebSocketGateway + + class GatewayRuntime(AsyncNewLoopRuntime): """ The Gateway Runtime that starts a gateway pod. From ffadb73d439eba9b519179ff409786248b90eab7 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 5 Oct 2022 16:04:20 +0200 Subject: [PATCH 50/79] fix(instrumentation): correctly handle default executor runtime_args --- jina/serve/executors/__init__.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 709bedef6bd01..5f53cdb685556 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -181,24 +181,16 @@ def _init_monitoring(self): self._summary_method = None self._metrics_buffer = None - def _init_instrumentation(self, _runtime_args: Optional[Dict]): - instrumentating_module_name = ( - _runtime_args.name - if hasattr(_runtime_args, 'name') - else self.__class__.__name__ - ) + def _init_instrumentation(self, _runtime_args: Optional[Dict] = {}): + instrumentating_module_name = _runtime_args.get('name', self.__class__.__name__) - self.tracer_provider = ( - _runtime_args['tracer_provider'] - if hasattr(_runtime_args, 'tracer_provider') - else trace.NoOpTracerProvider() + self.tracer_provider = _runtime_args.get( + 'tracer_provider', trace.NoOpTracerProvider() ) self.tracer = self.tracer_provider.get_tracer(instrumentating_module_name) - self.meter_provider = ( - _runtime_args['meter_provider'] - if hasattr(_runtime_args, 'meter_provider') - else metrics.NoOpMeterProvider() + self.meter_provider = _runtime_args.get( + 'meter_provider', metrics.NoOpMeterProvider() ) self.meter = self.meter_provider.get_meter(instrumentating_module_name) From 3f6eeff7a04888418105214706771773e1d9cd2f Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 5 Oct 2022 16:42:48 +0200 Subject: [PATCH 51/79] test(instrumentation): add integration tests for grpc, http and websocket instrumentation --- tests/integration/instrumentation/__init__.py | 79 +++++++++++++++++++ tests/integration/instrumentation/conftest.py | 24 ++++++ .../instrumentation/docker-compose.yml | 24 ++++++ .../instrumentation/otel-collector-config.yml | 26 ++++++ .../test_grpc_gateway_instrumentation.py | 61 ++++++++++++++ .../test_http_gateway_instrumentation.py | 61 ++++++++++++++ .../test_websocket_gateway_instrumentation.py | 63 +++++++++++++++ 7 files changed, 338 insertions(+) create mode 100644 tests/integration/instrumentation/__init__.py create mode 100644 tests/integration/instrumentation/conftest.py create mode 100644 tests/integration/instrumentation/docker-compose.yml create mode 100644 tests/integration/instrumentation/otel-collector-config.yml create mode 100644 tests/integration/instrumentation/test_grpc_gateway_instrumentation.py create mode 100644 tests/integration/instrumentation/test_http_gateway_instrumentation.py create mode 100644 tests/integration/instrumentation/test_websocket_gateway_instrumentation.py diff --git a/tests/integration/instrumentation/__init__.py b/tests/integration/instrumentation/__init__.py new file mode 100644 index 0000000000000..a274522b641e5 --- /dev/null +++ b/tests/integration/instrumentation/__init__.py @@ -0,0 +1,79 @@ +from typing import Dict, Optional + +from docarray import DocumentArray +from opentelemetry.context.context import Context + +from jina import Executor, requests + + +def get_traces(service): + import requests + + response = requests.get(f'http://localhost:16686/api/traces?service={service}') + response.raise_for_status() + return response.json().get('data', []) or [] + + +def _get_trace_id(any_object): + return any_object.get('traceID', '') + + +def get_trace_ids(traces): + trace_ids = set() + for trace in traces: + trace_ids.add(_get_trace_id(trace)) + for span in trace['spans']: + trace_ids.add(_get_trace_id(span)) + + return trace_ids + + +def partition_spans_by_kind(traces): + '''Returns three lists each containing spans of kind SpanKind.SERVER, SpanKind.CLIENT and SpandKind.INTERNAL''' + server_spans = [] + client_spans = [] + internal_spans = [] + + for trace in traces: + for span in trace['spans']: + for tag in span['tags']: + if 'span.kind' == tag.get('key', ''): + span_kind = tag.get('value', '') + if 'server' == span_kind: + server_spans.append(span) + elif 'client' == span_kind: + client_spans.append(span) + elif 'internal' == span_kind: + internal_spans.append(span) + + return (server_spans, client_spans, internal_spans) + + +class ExecutorTestWithTracing(Executor): + def __init__( + self, + metas: Optional[Dict] = None, + requests: Optional[Dict] = None, + runtime_args: Optional[Dict] = None, + workspace: Optional[str] = None, + **kwargs, + ): + super().__init__(metas, requests, runtime_args, workspace, **kwargs) + self.docs_counter = self.meter.create_counter(name='docs_counter') + + @requests(on='/index') + def empty(self, docs: 'DocumentArray', otel_context: Context, **kwargs): + with self.tracer.start_as_current_span('dummy', context=otel_context) as span: + span.set_attribute('len_docs', len(docs)) + self.docs_counter.add(len(docs)) + return docs + + +def get_services(): + import requests + + response = requests.get('http://localhost:16686/api/services') + response.raise_for_status() + response_json = response.json() + services = response_json.get('data', []) or [] + return [service for service in services if service != 'jaeger-query'] diff --git a/tests/integration/instrumentation/conftest.py b/tests/integration/instrumentation/conftest.py new file mode 100644 index 0000000000000..54802ceb688a4 --- /dev/null +++ b/tests/integration/instrumentation/conftest.py @@ -0,0 +1,24 @@ +import os +import time + +import pytest + +from jina.logging.logger import JinaLogger + + +@pytest.fixture +def logger(): + return JinaLogger('instrumentation-testing') + + +@pytest.fixture(scope='module') +def otlp_collector(): + file_dir = os.path.dirname(__file__) + os.system( + f"docker-compose -f {os.path.join(file_dir, 'docker-compose.yml')} up -d --remove-orphans" + ) + time.sleep(1) + yield + os.system( + f"docker-compose -f {os.path.join(file_dir, 'docker-compose.yml')} down --remove-orphans" + ) diff --git a/tests/integration/instrumentation/docker-compose.yml b/tests/integration/instrumentation/docker-compose.yml new file mode 100644 index 0000000000000..b8936584042ec --- /dev/null +++ b/tests/integration/instrumentation/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3" +services: + # Jaeger + jaeger: + image: jaegertracing/all-in-one:latest + ports: + - "16686:16686" + - "14250" + + otel-collector: + image: otel/opentelemetry-collector:0.61.0 + command: [ "--config=/etc/otel-collector-config.yml" ] + volumes: + - ./otel-collector-config.yml:/etc/otel-collector-config.yml + ports: + - "1888:1888" # pprof extension + - "8888:8888" # Prometheus metrics exposed by the collector + - "8889:8889" # Prometheus exporter metrics + - "13133:13133" # health_check extension + - "55679:55679" # zpages extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + depends_on: + - jaeger diff --git a/tests/integration/instrumentation/otel-collector-config.yml b/tests/integration/instrumentation/otel-collector-config.yml new file mode 100644 index 0000000000000..b7d34404d555c --- /dev/null +++ b/tests/integration/instrumentation/otel-collector-config.yml @@ -0,0 +1,26 @@ +receivers: + otlp: + protocols: + grpc: + +exporters: + jaeger: + endpoint: jaeger:14250 + tls: + insecure: true + +processors: + batch: + +extensions: + health_check: + pprof: + zpages: + +service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [otlp] + exporters: [jaeger] + processors: [batch] \ No newline at end of file diff --git a/tests/integration/instrumentation/test_grpc_gateway_instrumentation.py b/tests/integration/instrumentation/test_grpc_gateway_instrumentation.py new file mode 100644 index 0000000000000..fad5edff49841 --- /dev/null +++ b/tests/integration/instrumentation/test_grpc_gateway_instrumentation.py @@ -0,0 +1,61 @@ +import time + +import pytest + +from jina import Client, Flow +from tests.integration.instrumentation import ( + ExecutorTestWithTracing, + get_services, + get_trace_ids, + get_traces, + partition_spans_by_kind, +) + + +@pytest.mark.skip +def test_grpc_gateway_instrumentation(logger, otlp_collector): + f = Flow( + protocol='grpc', + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ).add( + uses=ExecutorTestWithTracing, + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + + f.start() + + c = Client( + host=f'grpc://localhost:{f.port}', + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + c.post( + f'/index', + {'data': [{'text': 'text_input'}]}, + ) + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + logger.info('waiting until traces and metrics are exported...') + time.sleep(8) + f.close() + + services = get_services() + expected_services = ['executor0/rep-0', 'gateway/rep-0', 'GRPCClient'] + assert len(services) == 3 + assert set(services).issubset(expected_services) + + client_traces = get_traces('GRPCClient') + (server_spans, client_spans, internal_spans) = partition_spans_by_kind( + client_traces + ) + assert len(server_spans) == 5 + assert len(client_spans) == 5 + assert len(internal_spans) == 1 + + trace_ids = get_trace_ids(client_traces) + assert len(trace_ids) == 1 diff --git a/tests/integration/instrumentation/test_http_gateway_instrumentation.py b/tests/integration/instrumentation/test_http_gateway_instrumentation.py new file mode 100644 index 0000000000000..c5ef57de0d18c --- /dev/null +++ b/tests/integration/instrumentation/test_http_gateway_instrumentation.py @@ -0,0 +1,61 @@ +import time + +import pytest + +from jina import Client, Flow +from tests.integration.instrumentation import ( + ExecutorTestWithTracing, + get_services, + get_trace_ids, + get_traces, + partition_spans_by_kind, +) + + +@pytest.mark.skip +def test_http_gateway_instrumentation(logger, otlp_collector): + f = Flow( + protocol='http', + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ).add( + uses=ExecutorTestWithTracing, + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + + f.start() + + c = Client( + host=f'http://localhost:{f.port}', + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + c.post( + f'/index', + {'data': [{'text': 'text_input'}]}, + ) + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + logger.info('waiting until traces and metrics are exported...') + time.sleep(8) + f.close() + + services = get_services() + expected_services = ['executor0/rep-0', 'gateway/rep-0', 'HTTPClient'] + assert len(services) == 3 + assert set(services).issubset(expected_services) + + client_traces = get_traces('HTTPClient') + (server_spans, client_spans, internal_spans) = partition_spans_by_kind( + client_traces + ) + assert len(server_spans) == 5 + assert len(client_spans) == 5 + assert len(internal_spans) == 4 + + trace_ids = get_trace_ids(client_traces) + assert len(trace_ids) == 1 diff --git a/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py b/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py new file mode 100644 index 0000000000000..51b8eb54da17d --- /dev/null +++ b/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py @@ -0,0 +1,63 @@ +import time +from audioop import mul + +import pytest + +from jina import Client, Flow +from tests.integration.instrumentation import ( + ExecutorTestWithTracing, + get_services, + get_trace_ids, + get_traces, + partition_spans_by_kind, +) + + +@pytest.mark.skip +def test_websocket_gateway_instrumentation(logger, otlp_collector): + f = Flow( + protocol='websocket', + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ).add( + uses=ExecutorTestWithTracing, + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + + f.start() + + c = Client( + host=f'ws://localhost:{f.port}', + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + c.post( + f'/index', + {'data': [{'text': 'text_input'}]}, + ) + + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + logger.info('waiting until traces and metrics are exported...') + time.sleep(8) + f.close() + + services = get_services() + expected_services = ['executor0/rep-0', 'gateway/rep-0', 'WebSocketClient'] + assert len(services) == 3 + assert set(services).issubset(expected_services) + + client_traces = get_traces('WebSocketClient') + (server_spans, client_spans, internal_spans) = partition_spans_by_kind( + client_traces + ) + assert len(server_spans) == 5 + assert len(client_spans) == 5 + assert len(internal_spans) == 6 + + trace_ids = get_trace_ids(client_traces) + assert len(trace_ids) == 1 From 6b359097797a98c2ffbedbc26013ca7720eae1bd Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Wed, 5 Oct 2022 17:56:51 +0200 Subject: [PATCH 52/79] test(instrumentation): parameterize instrumentation tests --- tests/integration/instrumentation/conftest.py | 7 +- .../test_flow_instrumentation.py | 72 +++++++++++++++++++ .../test_grpc_gateway_instrumentation.py | 61 ---------------- .../test_http_gateway_instrumentation.py | 29 +++----- .../test_websocket_gateway_instrumentation.py | 29 +++----- 5 files changed, 93 insertions(+), 105 deletions(-) create mode 100644 tests/integration/instrumentation/test_flow_instrumentation.py delete mode 100644 tests/integration/instrumentation/test_grpc_gateway_instrumentation.py diff --git a/tests/integration/instrumentation/conftest.py b/tests/integration/instrumentation/conftest.py index 54802ceb688a4..1a12e076f1226 100644 --- a/tests/integration/instrumentation/conftest.py +++ b/tests/integration/instrumentation/conftest.py @@ -6,12 +6,7 @@ from jina.logging.logger import JinaLogger -@pytest.fixture -def logger(): - return JinaLogger('instrumentation-testing') - - -@pytest.fixture(scope='module') +@pytest.fixture() def otlp_collector(): file_dir = os.path.dirname(__file__) os.system( diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py new file mode 100644 index 0000000000000..b376620232b6e --- /dev/null +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -0,0 +1,72 @@ +import time + +import pytest + +from jina import Client, Flow +from tests.integration.instrumentation import ( + ExecutorTestWithTracing, + get_services, + get_trace_ids, + get_traces, + partition_spans_by_kind, +) + + +@pytest.mark.parametrize( + 'test_args', + [ + {'protocol': 'grpc', 'client_type': 'GRPCClient', 'num_internal_spans': 1}, + {'protocol': 'http', 'client_type': 'HTTPClient', 'num_internal_spans': 4}, + { + 'protocol': 'websocket', + 'client_type': 'WebSocketClient', + 'num_internal_spans': 6, + }, + ], +) +def test_grpc_gateway_instrumentation(otlp_collector, test_args): + import multiprocessing + + multiprocessing.set_start_method('spawn', force=True) + protocol = test_args['protocol'] + client_type = test_args['client_type'] + num_internal_spans = test_args['num_internal_spans'] + + f = Flow( + protocol=protocol, + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ).add( + uses=ExecutorTestWithTracing, + opentelemetry_tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ) + + with f: + from jina import DocumentArray + + f.post( + f'/index', + DocumentArray.empty(2), + ) + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + time.sleep(8) + + services = get_services() + expected_services = ['executor0/rep-0', 'gateway/rep-0', client_type] + assert len(services) == 3 + assert set(services).issubset(expected_services) + + client_traces = get_traces(client_type) + (server_spans, client_spans, internal_spans) = partition_spans_by_kind( + client_traces + ) + assert len(server_spans) == 5 + assert len(client_spans) == 5 + assert len(internal_spans) == num_internal_spans + + trace_ids = get_trace_ids(client_traces) + assert len(trace_ids) == 1 diff --git a/tests/integration/instrumentation/test_grpc_gateway_instrumentation.py b/tests/integration/instrumentation/test_grpc_gateway_instrumentation.py deleted file mode 100644 index fad5edff49841..0000000000000 --- a/tests/integration/instrumentation/test_grpc_gateway_instrumentation.py +++ /dev/null @@ -1,61 +0,0 @@ -import time - -import pytest - -from jina import Client, Flow -from tests.integration.instrumentation import ( - ExecutorTestWithTracing, - get_services, - get_trace_ids, - get_traces, - partition_spans_by_kind, -) - - -@pytest.mark.skip -def test_grpc_gateway_instrumentation(logger, otlp_collector): - f = Flow( - protocol='grpc', - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ).add( - uses=ExecutorTestWithTracing, - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ) - - f.start() - - c = Client( - host=f'grpc://localhost:{f.port}', - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ) - c.post( - f'/index', - {'data': [{'text': 'text_input'}]}, - ) - # give some time for the tracing and metrics exporters to finish exporting. - # the client is slow to export the data - logger.info('waiting until traces and metrics are exported...') - time.sleep(8) - f.close() - - services = get_services() - expected_services = ['executor0/rep-0', 'gateway/rep-0', 'GRPCClient'] - assert len(services) == 3 - assert set(services).issubset(expected_services) - - client_traces = get_traces('GRPCClient') - (server_spans, client_spans, internal_spans) = partition_spans_by_kind( - client_traces - ) - assert len(server_spans) == 5 - assert len(client_spans) == 5 - assert len(internal_spans) == 1 - - trace_ids = get_trace_ids(client_traces) - assert len(trace_ids) == 1 diff --git a/tests/integration/instrumentation/test_http_gateway_instrumentation.py b/tests/integration/instrumentation/test_http_gateway_instrumentation.py index c5ef57de0d18c..78e50b2cd6950 100644 --- a/tests/integration/instrumentation/test_http_gateway_instrumentation.py +++ b/tests/integration/instrumentation/test_http_gateway_instrumentation.py @@ -13,9 +13,9 @@ @pytest.mark.skip -def test_http_gateway_instrumentation(logger, otlp_collector): +def test_http_gateway_instrumentation(otlp_collector): f = Flow( - protocol='http', + protocol='grpc', opentelemetry_tracing=True, span_exporter_host='localhost', span_exporter_port=4317, @@ -26,23 +26,14 @@ def test_http_gateway_instrumentation(logger, otlp_collector): span_exporter_port=4317, ) - f.start() - - c = Client( - host=f'http://localhost:{f.port}', - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ) - c.post( - f'/index', - {'data': [{'text': 'text_input'}]}, - ) - # give some time for the tracing and metrics exporters to finish exporting. - # the client is slow to export the data - logger.info('waiting until traces and metrics are exported...') - time.sleep(8) - f.close() + with f: + # f.post( + # f'/index', + # {'data': [{'text': 'text_input'}]}, + # ) + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + time.sleep(8) services = get_services() expected_services = ['executor0/rep-0', 'gateway/rep-0', 'HTTPClient'] diff --git a/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py b/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py index 51b8eb54da17d..d90fc67668774 100644 --- a/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py +++ b/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py @@ -14,9 +14,9 @@ @pytest.mark.skip -def test_websocket_gateway_instrumentation(logger, otlp_collector): +def test_websocket_gateway_instrumentation(otlp_collector): f = Flow( - protocol='websocket', + protocol='grpc', opentelemetry_tracing=True, span_exporter_host='localhost', span_exporter_port=4317, @@ -27,24 +27,15 @@ def test_websocket_gateway_instrumentation(logger, otlp_collector): span_exporter_port=4317, ) - f.start() + with f: + # f.post( + # f'/index', + # {'data': [{'text': 'text_input'}]}, + # ) - c = Client( - host=f'ws://localhost:{f.port}', - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ) - c.post( - f'/index', - {'data': [{'text': 'text_input'}]}, - ) - - # give some time for the tracing and metrics exporters to finish exporting. - # the client is slow to export the data - logger.info('waiting until traces and metrics are exported...') - time.sleep(8) - f.close() + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + time.sleep(8) services = get_services() expected_services = ['executor0/rep-0', 'gateway/rep-0', 'WebSocketClient'] From 29063696e10095a03a3cbcf044bd101372084e37 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 09:06:10 +0200 Subject: [PATCH 53/79] test(instrumentation): remove outdated tests replaced by parametrized test --- .../test_http_gateway_instrumentation.py | 52 ----- .../test_websocket_gateway_instrumentation.py | 54 ------ tests/unit/serve/runtimes/gateway/__init__.py | 180 ------------------ .../gateway/grpc/test_instrumentation.py | 89 --------- .../gateway/http/test_instrumentation.py | 92 --------- .../runtimes/gateway/websocket/__init__.py | 0 .../gateway/websocket/test_instrumentation.py | 92 --------- 7 files changed, 559 deletions(-) delete mode 100644 tests/integration/instrumentation/test_http_gateway_instrumentation.py delete mode 100644 tests/integration/instrumentation/test_websocket_gateway_instrumentation.py delete mode 100644 tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py delete mode 100644 tests/unit/serve/runtimes/gateway/http/test_instrumentation.py delete mode 100644 tests/unit/serve/runtimes/gateway/websocket/__init__.py delete mode 100644 tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py diff --git a/tests/integration/instrumentation/test_http_gateway_instrumentation.py b/tests/integration/instrumentation/test_http_gateway_instrumentation.py deleted file mode 100644 index 78e50b2cd6950..0000000000000 --- a/tests/integration/instrumentation/test_http_gateway_instrumentation.py +++ /dev/null @@ -1,52 +0,0 @@ -import time - -import pytest - -from jina import Client, Flow -from tests.integration.instrumentation import ( - ExecutorTestWithTracing, - get_services, - get_trace_ids, - get_traces, - partition_spans_by_kind, -) - - -@pytest.mark.skip -def test_http_gateway_instrumentation(otlp_collector): - f = Flow( - protocol='grpc', - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ).add( - uses=ExecutorTestWithTracing, - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ) - - with f: - # f.post( - # f'/index', - # {'data': [{'text': 'text_input'}]}, - # ) - # give some time for the tracing and metrics exporters to finish exporting. - # the client is slow to export the data - time.sleep(8) - - services = get_services() - expected_services = ['executor0/rep-0', 'gateway/rep-0', 'HTTPClient'] - assert len(services) == 3 - assert set(services).issubset(expected_services) - - client_traces = get_traces('HTTPClient') - (server_spans, client_spans, internal_spans) = partition_spans_by_kind( - client_traces - ) - assert len(server_spans) == 5 - assert len(client_spans) == 5 - assert len(internal_spans) == 4 - - trace_ids = get_trace_ids(client_traces) - assert len(trace_ids) == 1 diff --git a/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py b/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py deleted file mode 100644 index d90fc67668774..0000000000000 --- a/tests/integration/instrumentation/test_websocket_gateway_instrumentation.py +++ /dev/null @@ -1,54 +0,0 @@ -import time -from audioop import mul - -import pytest - -from jina import Client, Flow -from tests.integration.instrumentation import ( - ExecutorTestWithTracing, - get_services, - get_trace_ids, - get_traces, - partition_spans_by_kind, -) - - -@pytest.mark.skip -def test_websocket_gateway_instrumentation(otlp_collector): - f = Flow( - protocol='grpc', - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ).add( - uses=ExecutorTestWithTracing, - opentelemetry_tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, - ) - - with f: - # f.post( - # f'/index', - # {'data': [{'text': 'text_input'}]}, - # ) - - # give some time for the tracing and metrics exporters to finish exporting. - # the client is slow to export the data - time.sleep(8) - - services = get_services() - expected_services = ['executor0/rep-0', 'gateway/rep-0', 'WebSocketClient'] - assert len(services) == 3 - assert set(services).issubset(expected_services) - - client_traces = get_traces('WebSocketClient') - (server_spans, client_spans, internal_spans) = partition_spans_by_kind( - client_traces - ) - assert len(server_spans) == 5 - assert len(client_spans) == 5 - assert len(internal_spans) == 6 - - trace_ids = get_trace_ids(client_traces) - assert len(trace_ids) == 1 diff --git a/tests/unit/serve/runtimes/gateway/__init__.py b/tests/unit/serve/runtimes/gateway/__init__.py index 15716e9966cd3..e69de29bb2d1d 100644 --- a/tests/unit/serve/runtimes/gateway/__init__.py +++ b/tests/unit/serve/runtimes/gateway/__init__.py @@ -1,180 +0,0 @@ -import json -import multiprocessing -from typing import Dict, Optional, Sequence, Tuple - -from docarray import DocumentArray -from opentelemetry.context.context import Context -from opentelemetry.sdk.metrics._internal import MeterProvider -from opentelemetry.sdk.metrics._internal.export import ( - InMemoryMetricReader, - MetricExporter, - MetricExportResult, - MetricReader, -) -from opentelemetry.sdk.metrics.export import MetricsData, PeriodicExportingMetricReader -from opentelemetry.sdk.trace import ReadableSpan, TracerProvider, export -from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult -from opentelemetry.test.test_base import TestBase - -from jina import Executor, requests - - -class ExecutorTestWithTracing(Executor): - def __init__( - self, - metas: Optional[Dict] = None, - requests: Optional[Dict] = None, - runtime_args: Optional[Dict] = None, - workspace: Optional[str] = None, - **kwargs, - ): - super().__init__(metas, requests, runtime_args, workspace, **kwargs) - self.docs_counter = self.meter.create_counter(name='docs_counter') - - @requests(on='/index') - def empty(self, docs: 'DocumentArray', otel_context: Context, **kwargs): - with self.tracer.start_as_current_span('dummy', context=otel_context) as span: - span.set_attribute('len_docs', len(docs)) - self.docs_counter.add(len(docs)) - return docs - - -class CustomSpanExporter(SpanExporter): - """Implementation of :class:`.SpanExporter` that stores spans as json in a multiprocessing.list(). - - This class can be used for testing purposes. It stores the exported spans - in a list in memory that can be retrieved using the - :func:`.get_finished_spans` method. - """ - - def __init__(self): - self._mp_manager = multiprocessing.Manager() - self._finished_spans = self._mp_manager.list() - - def clear(self): - """Clear list of collected spans.""" - self._finished_spans[:] = [] - - def get_finished_spans(self): - """Get list of collected spans.""" - return tuple(self._finished_spans) - - def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: - """Stores a list of spans in memory.""" - for span in spans: - self._finished_spans.append(span.to_json(indent=None)) - return SpanExportResult.SUCCESS - - -class CustomMetricExporter(MetricExporter): - """Implementation of `MetricReader` that returns its metrics from :func:`get_metrics_data`. - There are two internal data value holders from the multiprocessing library. - The "self._collector" multiprocessing.Value type holds the latest metric export. This can be useful in some situations to check - the latest export but it can be overwritten easily by another operation or metric provider reset. - The "self._docs_count_data_points" holds the data points of a specific "docs_counter" metric. - - This is useful for e.g. unit tests. - """ - - def __init__( - self, - ): - super().__init__() - self._manager = multiprocessing.Manager() - self._collector = self._manager.Value('s', '') - self._docs_count_data_points = self._manager.list() - - def export( - self, - metrics_data: MetricsData, - timeout_millis: float = 500, - **kwargs, - ) -> MetricExportResult: - for resource_metrics in metrics_data.resource_metrics: - for scope_metrics in resource_metrics.scope_metrics: - for metric in scope_metrics.metrics: - if metric.name == 'docs_counter': - self._docs_count_data_points.append( - metric.data.to_json(indent=None) - ) - - self._collector.value = metrics_data.to_json(indent=None) - return MetricExportResult.SUCCESS - - def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: - pass - - def force_flush(self, timeout_millis: float = 10_000) -> bool: - return True - - def get_metrics_data(self): - return self._collector.value - - def get_docs_count_data_points(self): - return self._docs_count_data_points - - -class InstrumentationTestBase(TestBase): - custom_metric_exporter = CustomMetricExporter() - - @staticmethod - def create_tracer_provider(**kwargs): - """Helper to create a configured tracer provider. - - Creates and configures a `TracerProvider` with a - `SimpleSpanProcessor` and a `InMemorySpanExporter`. - All the parameters passed are forwarded to the TracerProvider - constructor. - - Returns: - A list with the tracer provider in the first element and the - in-memory span exporter in the second. - """ - - memory_exporter = CustomSpanExporter() - - tracer_provider = TracerProvider(**kwargs) - span_processor = export.SimpleSpanProcessor(memory_exporter) - tracer_provider.add_span_processor(span_processor) - - return tracer_provider, memory_exporter - - @staticmethod - def create_meter_provider( - **kwargs, - ) -> Tuple[MeterProvider, MetricReader]: - """Helper to create a configured meter provider - Creates a `MeterProvider` and an `InMemoryMetricReader`. - Returns: - A tuple with the meter provider in the first element and the - in-memory metrics exporter in the second - """ - memory_reader = InMemoryMetricReader() - custom_metric_reader = PeriodicExportingMetricReader( - InstrumentationTestBase.custom_metric_exporter, export_interval_millis=500 - ) - - metric_readers = kwargs.get("metric_readers", []) - metric_readers.append(memory_reader) - metric_readers.append(custom_metric_reader) - kwargs["metric_readers"] = metric_readers - meter_provider = MeterProvider(**kwargs) - return meter_provider, memory_reader - - def partition_spans_by_kind(self): - '''Returns three lists each containing spans of kind SpanKind.SERVER, SpanKind.CLIENT and SpandKind.INTERNAL''' - server_spans = [] - client_spans = [] - internal_spans = [] - - for span_json in self.memory_exporter.get_finished_spans(): - span = json.loads(span_json) - span_kind = span.get('kind', '') - if 'SpanKind.SERVER' == span_kind: - server_spans.append(span) - elif 'SpanKind.CLIENT' == span_kind: - client_spans.append(span) - elif 'SpanKind.INTERNAL' == span_kind: - internal_spans.append(span) - - return (server_spans, client_spans, internal_spans) diff --git a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py deleted file mode 100644 index f845a702a7f4e..0000000000000 --- a/tests/unit/serve/runtimes/gateway/grpc/test_instrumentation.py +++ /dev/null @@ -1,89 +0,0 @@ -import time - -from jina import Client, Flow -from tests.unit.serve.runtimes.gateway import ( - ExecutorTestWithTracing, - InstrumentationTestBase, -) - - -class TestGrpcGatewayTracing(InstrumentationTestBase): - def setUp(self): - super().setUp() - self.docs_input = {'data': [{'text': 'text_input'}]} - - def tearDown(self): - super().tearDown() - - def test_http_span_attributes_default_args(self): - f = Flow(protocol='grpc').add() - - with f: - c = Client( - host=f'grpc://localhost:{f.port}', - ) - c.post( - f'/index', - self.docs_input, - ) - # give some time for the tracing and metrics exporters to finish exporting. - time.sleep(1) - ( - server_spans, - client_spans, - internal_spans, - ) = self.partition_spans_by_kind() - self.assertEqual(0, len(server_spans)) - # There are currently 5 SpanKind.CLIENT spans produced by the - # a. /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo (2) - # b. /jina.JinaDiscoverEndpointsRPC/endpoint_discovery (1) - # c. /jina.JinaSingleDataRequestRPC/process_single_data (1) - # d. /jina.JinaRPC/Call (1) - # that cannot yet be completely disabled because the GrpcConnectionPool.get_grpc_channel - # method is a static method. This means that until a concrete '.set_tracer_provider' gets executed, the tracer will - # be a default ProxyTracerProvider. - self.assertEqual(5, len(client_spans)) - self.assertEqual(0, len(internal_spans)) - - def test_http_span_attributes_with_executor(self): - f = Flow( - protocol='grpc', opentelemetry_tracing=True, opentelemetry_metrics=True - ).add(uses=ExecutorTestWithTracing) - - with f: - c = Client( - host=f'grpc://localhost:{f.port}', - ) - c.post( - f'/index', - self.docs_input, - ) - # give some time for the tracing and metrics exporters to finish exporting. - time.sleep(1) - - ( - server_spans, - client_spans, - internal_spans, - ) = self.partition_spans_by_kind() - - # There only 1 dummy span created in the Executor method - self.assertEqual(1, len(internal_spans)) - for internal_span in internal_spans: - if internal_span.get('name', '') == 'dummy': - self.assertEqual( - len(self.docs_input), internal_span['attributes']['len_docs'] - ) - - # The 5 spans are as described in the above test. - self.assertEqual(5, len(client_spans)) - # The SpanKind.SERVER spans for each of the above 5 SpanKind.CLIENT requests - self.assertEqual(5, len(server_spans)) - self.assertEqual(11, len(self.get_finished_spans())) - - self.assertGreater( - len( - TestGrpcGatewayTracing.custom_metric_exporter.get_docs_count_data_points() - ), - 0, - ) diff --git a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py deleted file mode 100644 index 98326d304bb7d..0000000000000 --- a/tests/unit/serve/runtimes/gateway/http/test_instrumentation.py +++ /dev/null @@ -1,92 +0,0 @@ -import time - -import requests as req - -from jina import Flow -from tests.unit.serve.runtimes.gateway import ( - ExecutorTestWithTracing, - InstrumentationTestBase, -) - - -class TestHttpGatewayTracing(InstrumentationTestBase): - def setUp(self): - super().setUp() - self.docs_input = {'data': [{'text': 'text_input'}]} - - def tearDown(self): - super().tearDown() - - def test_http_span_attributes_default_args(self): - f = Flow(protocol='http').add() - - with f: - req.post( - f'http://localhost:{f.port}/index', - json=self.docs_input, - ) - # give some time for the tracing and metrics exporters to finish exporting. - time.sleep(1) - ( - server_spans, - client_spans, - internal_spans, - ) = self.partition_spans_by_kind() - self.assertEqual(0, len(server_spans)) - # There are currently 4 SpanKind.CLIENT spans produced by the - # a. /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo (2) - # b. /jina.JinaDiscoverEndpointsRPC/endpoint_discovery (1) - # c. /jina.JinaSingleDataRequestRPC/process_single_data (1) - # that cannot yet be completely disabled because the GrpcConnectionPool.get_grpc_channel - # method is a static method. This means that until a concrete '.set_tracer_provider' gets executed, the tracer will - # be a default ProxyTracerProvider. - self.assertEqual(4, len(client_spans)) - self.assertEqual(0, len(internal_spans)) - self.assertEqual(4, len(self.get_finished_spans())) - - def test_http_span_attributes_with_executor(self): - f = Flow( - protocol='http', opentelemetry_tracing=True, opentelemetry_metrics=True - ).add(uses=ExecutorTestWithTracing, name='executortest') - - with f: - req.post( - f'http://localhost:{f.port}/index', - json=self.docs_input, - ) - # give some time for the tracing and metrics exporters to finish exporting. - time.sleep(1) - - ( - server_spans, - client_spans, - internal_spans, - ) = self.partition_spans_by_kind() - - self.assertEqual(4, len(internal_spans)) - for internal_span in internal_spans: - if internal_span.get('name', '') == 'dummy': - self.assertEqual( - len(self.docs_input), internal_span['attributes']['len_docs'] - ) - - # There are the usual 4 SpanKind.CLIENT spans as mentioned above. - self.assertEqual(4, len(client_spans)) - # There are 5 total spans. The CLIENT spans from the above are being traced correctly by the server. Apart - # from the spans mentioned in te above test, there is an extra span (expected) for the '/index' - # request from the client that is handled by the gateway http server> - self.assertEqual(5, len(server_spans)) - # The FASTApi app instrumentation tracks the server spans as SpanKind.INTERNAL. There are 4 spans for: - # 1. /index http receive - # 2. dummy operation in the Executor method - # 3. /index http send - # 4. /index http send - self.assertEqual(4, len(internal_spans)) - self.assertEqual(13, len(self.get_finished_spans())) - - self.assertGreater( - len( - TestHttpGatewayTracing.custom_metric_exporter.get_docs_count_data_points() - ), - 0, - ) diff --git a/tests/unit/serve/runtimes/gateway/websocket/__init__.py b/tests/unit/serve/runtimes/gateway/websocket/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py b/tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py deleted file mode 100644 index 6e2b536f934be..0000000000000 --- a/tests/unit/serve/runtimes/gateway/websocket/test_instrumentation.py +++ /dev/null @@ -1,92 +0,0 @@ -import time - -from jina import Client, Flow -from tests.unit.serve.runtimes.gateway import ( - ExecutorTestWithTracing, - InstrumentationTestBase, -) - - -class TestWebsocketGatewayTracing(InstrumentationTestBase): - def setUp(self): - super().setUp() - self.docs_input = {'data': [{'text': 'text_input'}]} - - def tearDown(self): - super().tearDown() - - def test_http_span_attributes_default_args(self): - f = Flow(protocol='websocket').add() - - with f: - c = Client( - host=f'ws://localhost:{f.port}', - ) - c.post( - f'/index', - self.docs_input, - ) - # give some time for the tracing and metrics exporters to finish exporting. - time.sleep(1) - ( - server_spans, - client_spans, - internal_spans, - ) = self.partition_spans_by_kind() - self.assertEqual(0, len(server_spans)) - # There are currently 5 SpanKind.CLIENT spans produced by the - # a. /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo (2) - # b. /jina.JinaDiscoverEndpointsRPC/endpoint_discovery (1) - # c. /jina.JinaSingleDataRequestRPC/process_single_data (1) - # d. HTTP GET (1) - # that cannot yet be completely disabled because the GrpcConnectionPool.get_grpc_channel - # method is a static method. This means that until a concrete '.set_tracer_provider' gets executed, the tracer will - # be a default ProxyTracerProvider. - self.assertEqual(5, len(client_spans)) - self.assertEqual(0, len(internal_spans)) - - def test_http_span_attributes_with_executor(self): - f = Flow( - protocol='websocket', opentelemetry_tracing=True, opentelemetry_metrics=True - ).add(uses=ExecutorTestWithTracing) - - with f: - c = Client( - host=f'ws://localhost:{f.port}', - ) - c.post( - f'/index', - self.docs_input, - ) - # give some time for the tracing and metrics exporters to finish exporting. - time.sleep(1) - - ( - server_spans, - client_spans, - internal_spans, - ) = self.partition_spans_by_kind() - - # There are 6 SpanKind.INTERNAL spans created for - # a. / websocket receive (3) - # b. / websocket send (2) - # c. dummy (1) span created by the Executor - self.assertEqual(6, len(internal_spans)) - for internal_span in internal_spans: - if internal_span.get('name', '') == 'dummy': - self.assertEqual( - len(self.docs_input), internal_span['attributes']['len_docs'] - ) - - # The 5 spans are as described in the above test. - self.assertEqual(5, len(client_spans)) - # The SpanKind.SERVER spans for each of the above 5 SpanKind.CLIENT requests - self.assertEqual(5, len(server_spans)) - self.assertEqual(16, len(self.get_finished_spans())) - - self.assertGreater( - len( - TestWebsocketGatewayTracing.custom_metric_exporter.get_docs_count_data_points() - ), - 0, - ) From f1ad7a2c6776f0bf54fc7d1897f26a610278b687 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 09:40:15 +0200 Subject: [PATCH 54/79] fix(instrumentation): fix executor instrumentation setup --- jina/serve/executors/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 5f53cdb685556..cccf170bd3fa4 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -135,7 +135,7 @@ def __init__( self._add_requests(requests) self._add_runtime_args(runtime_args) self._init_monitoring() - self._init_instrumentation(runtime_args) + self._init_instrumentation(runtime_args or {}) self._init_workspace = workspace self.logger = JinaLogger(self.__class__.__name__) if __dry_run_endpoint__ not in self.requests: @@ -181,7 +181,7 @@ def _init_monitoring(self): self._summary_method = None self._metrics_buffer = None - def _init_instrumentation(self, _runtime_args: Optional[Dict] = {}): + def _init_instrumentation(self, _runtime_args: Dict = {}): instrumentating_module_name = _runtime_args.get('name', self.__class__.__name__) self.tracer_provider = _runtime_args.get( From d7bb8d9b0374772df1b6db9edc3457b116751ea6 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 09:40:51 +0200 Subject: [PATCH 55/79] fix(instrumentation): force spawn process when running flows in parametrized test --- tests/integration/grpc_bytes_census/test_bytes_census.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration/grpc_bytes_census/test_bytes_census.py b/tests/integration/grpc_bytes_census/test_bytes_census.py index 87aec2982be32..3c4c59a0970ca 100644 --- a/tests/integration/grpc_bytes_census/test_bytes_census.py +++ b/tests/integration/grpc_bytes_census/test_bytes_census.py @@ -10,6 +10,10 @@ @pytest.mark.parametrize('inputs', [None, DocumentArray.empty(10)]) def test_grpc_census(inputs): + import multiprocessing + + multiprocessing.set_start_method('spawn', force=True) + assert int(os.environ.get('JINA_GRPC_SEND_BYTES', 0)) == 0 assert int(os.environ.get('JINA_GRPC_RECV_BYTES', 0)) == 0 with Flow().add().add() as f: From 5e31dcaf867c6de3877396b9d71fce7084848820 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 10:07:31 +0200 Subject: [PATCH 56/79] feat(instrumentation): omit opentelemetry from cli args --- docs/fundamentals/flow/executor-args.md | 4 +-- docs/fundamentals/flow/gateway-args.md | 4 +-- jina/clients/__init__.py | 12 +++---- jina/clients/base/__init__.py | 4 +-- jina/orchestrate/flow/base.py | 36 +++++++++---------- jina/parsers/client.py | 8 ++--- jina/parsers/orchestrate/pod.py | 8 ++--- jina/serve/gateway.py | 6 ++-- jina/serve/instrumentation/__init__.py | 21 +++++------ jina/serve/runtimes/asyncio.py | 4 +-- jina/serve/runtimes/gateway/__init__.py | 2 +- jina/serve/runtimes/gateway/http/app.py | 6 ++-- jina/serve/runtimes/gateway/http/gateway.py | 2 +- jina/serve/runtimes/gateway/websocket/app.py | 6 ++-- .../runtimes/gateway/websocket/gateway.py | 2 +- jina_cli/autocomplete.py | 22 ++++++------ .../test_flow_instrumentation.py | 4 +-- 17 files changed, 75 insertions(+), 76 deletions(-) diff --git a/docs/fundamentals/flow/executor-args.md b/docs/fundamentals/flow/executor-args.md index 17f234dc66ff3..3a7b3b3fc5e0b 100644 --- a/docs/fundamentals/flow/executor-args.md +++ b/docs/fundamentals/flow/executor-args.md @@ -35,10 +35,10 @@ | `port_monitoring` | The port on which the prometheus server is exposed, default is a random port between [49152, 65535] | `string` | `random in [49152, 65535]` | | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | -| `opentelemetry_tracing` | If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `tracing` | If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `span_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | | `span_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | -| `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `metrics` | If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `metrics_exporter_host` | If tracing is enabled, this hostname will be used to configure the metrics exporter agent. | `string` | `None` | | `metrics_exporter_port` | If tracing is enabled, this port will be used to configure the metrics exporter agent. | `number` | `None` | | `install_requirements` | If set, install `requirements.txt` in the Hub Executor bundle to local | `boolean` | `False` | diff --git a/docs/fundamentals/flow/gateway-args.md b/docs/fundamentals/flow/gateway-args.md index 1740694c59dcf..941a5dda6f02a 100644 --- a/docs/fundamentals/flow/gateway-args.md +++ b/docs/fundamentals/flow/gateway-args.md @@ -50,9 +50,9 @@ | `port_monitoring` | The port on which the prometheus server is exposed, default is a random port between [49152, 65535] | `string` | `random in [49152, 65535]` | | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | -| `opentelemetry_tracing` | If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `tracing` | If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `span_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | | `span_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | -| `opentelemetry_metrics` | If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | +| `metrics` | If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `metrics_exporter_host` | If tracing is enabled, this hostname will be used to configure the metrics exporter agent. | `string` | `None` | | `metrics_exporter_port` | If tracing is enabled, this port will be used to configure the metrics exporter agent. | `number` | `None` | \ No newline at end of file diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 429fda338e281..129be1a4a262d 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -20,16 +20,16 @@ def Client( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', + metrics: Optional[bool] = False, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, - opentelemetry_metrics: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, span_exporter_host: Optional[str] = None, span_exporter_port: Optional[int] = None, tls: Optional[bool] = False, + tracing: Optional[bool] = False, **kwargs ) -> Union[ 'AsyncWebSocketClient', @@ -43,16 +43,16 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :return: the new Client object .. # noqa: DAR202 @@ -93,16 +93,16 @@ def Client( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :return: the new Client object .. # noqa: DAR102 diff --git a/jina/clients/base/__init__.py b/jina/clients/base/__init__.py index cb172f2b824be..c075501135947 100644 --- a/jina/clients/base/__init__.py +++ b/jina/clients/base/__init__.py @@ -52,10 +52,10 @@ def __init__( name=self.args.name if hasattr(self.args, 'name') else self.__class__.__name__, - opentelemetry_tracing=self.args.opentelemetry_tracing, + tracing=self.args.tracing, span_exporter_host=self.args.span_exporter_host, span_exporter_port=self.args.span_exporter_port, - opentelemetry_metrics=self.args.opentelemetry_metrics, + metrics=self.args.metrics, metrics_exporter_host=self.args.metrics_exporter_host, metrics_exporter_port=self.args.metrics_exporter_port, ) diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index a972cabf670d2..71b361acb964b 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -115,32 +115,32 @@ def __init__( *, asyncio: Optional[bool] = False, host: Optional[str] = '0.0.0.0', + metrics: Optional[bool] = False, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, - opentelemetry_metrics: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, span_exporter_host: Optional[str] = None, span_exporter_port: Optional[int] = None, tls: Optional[bool] = False, + tracing: Optional[bool] = False, **kwargs, ): """Create a Flow. Flow is how Jina streamlines and scales Executors. This overloaded method provides arguments from `jina client` CLI. :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. .. # noqa: DAR202 .. # noqa: DAR101 @@ -174,6 +174,7 @@ def __init__( host: Optional[str] = '0.0.0.0', host_in: Optional[str] = '0.0.0.0', log_config: Optional[str] = None, + metrics: Optional[bool] = False, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, monitoring: Optional[bool] = False, @@ -181,8 +182,6 @@ def __init__( native: Optional[bool] = False, no_crud_endpoints: Optional[bool] = False, no_debug_endpoints: Optional[bool] = False, - opentelemetry_metrics: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, output_array_type: Optional[str] = None, polling: Optional[str] = 'ANY', port: Optional[int] = None, @@ -205,6 +204,7 @@ def __init__( timeout_ready: Optional[int] = 600000, timeout_send: Optional[int] = None, title: Optional[str] = None, + tracing: Optional[bool] = False, uses: Optional[Union[str, Type['BaseExecutor'], dict]] = None, uses_with: Optional[dict] = None, uvicorn_kwargs: Optional[dict] = None, @@ -244,6 +244,7 @@ def __init__( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param log_config: The YAML config of the logger used in this object. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics @@ -261,8 +262,6 @@ def __init__( Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -302,6 +301,7 @@ def __init__( :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param title: The title of this HTTP server. It will be used in automatics docs such as Swagger UI. + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param uses: The config of the gateway, it could be one of the followings: * the string literal of an Gateway class name * a Gateway YAML file (.yml, .yaml, .jaml) @@ -407,16 +407,16 @@ def __init__( :param asyncio: If set, then the input and output of this Client work in an asynchronous manner. :param host: The host address of the runtime, by default it is 0.0.0.0. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. :param cors: If set, a CORS middleware is added to FastAPI frontend to allow cross-origin access. :param deployments_addresses: dictionary JSON with the input addresses of each Deployment @@ -447,6 +447,7 @@ def __init__( :param host: The host address of the runtime, by default it is 0.0.0.0. :param host_in: The host address for binding to, by default it is 0.0.0.0 :param log_config: The YAML config of the logger used in this object. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics @@ -464,8 +465,6 @@ def __init__( Any executor that has `@requests(on=...)` bind with those values will receive data requests. :param no_debug_endpoints: If set, `/status` `/post` endpoints are removed from HTTP interface. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -505,6 +504,7 @@ def __init__( :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param title: The title of this HTTP server. It will be used in automatics docs such as Swagger UI. + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param uses: The config of the gateway, it could be one of the followings: * the string literal of an Gateway class name * a Gateway YAML file (.yml, .yaml, .jaml) @@ -875,13 +875,12 @@ def add( host_in: Optional[str] = '0.0.0.0', install_requirements: Optional[bool] = False, log_config: Optional[str] = None, + metrics: Optional[bool] = False, metrics_exporter_host: Optional[str] = None, metrics_exporter_port: Optional[int] = None, monitoring: Optional[bool] = False, name: Optional[str] = None, native: Optional[bool] = False, - opentelemetry_metrics: Optional[bool] = False, - opentelemetry_tracing: Optional[bool] = False, output_array_type: Optional[str] = None, polling: Optional[str] = 'ANY', port: Optional[int] = None, @@ -900,6 +899,7 @@ def add( timeout_ready: Optional[int] = 600000, timeout_send: Optional[int] = None, tls: Optional[bool] = False, + tracing: Optional[bool] = False, upload_files: Optional[List[str]] = None, uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', uses_after: Optional[Union[str, Type['BaseExecutor'], dict]] = None, @@ -943,6 +943,7 @@ def add( :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local :param log_config: The YAML config of the logger used in this object. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics @@ -956,8 +957,6 @@ def add( When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -992,6 +991,7 @@ def add( :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param tls: If set, connect to deployment using tls encryption + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param upload_files: The files on the host to be uploaded to the remote workspace. This can be useful when your Deployment has more file dependencies beyond a single YAML file, e.g. @@ -1098,6 +1098,7 @@ def add( :param host_in: The host address for binding to, by default it is 0.0.0.0 :param install_requirements: If set, install `requirements.txt` in the Hub Executor bundle to local :param log_config: The YAML config of the logger used in this object. + :param metrics: If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. :param metrics_exporter_host: If tracing is enabled, this hostname will be used to configure the metrics exporter agent. :param metrics_exporter_port: If tracing is enabled, this port will be used to configure the metrics exporter agent. :param monitoring: If set, spawn an http server with a prometheus endpoint to expose metrics @@ -1111,8 +1112,6 @@ def add( When not given, then the default naming strategy will apply. :param native: If set, only native Executors is allowed, and the Executor is always run inside WorkerRuntime. - :param opentelemetry_metrics: If set, real implementation of the metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. - :param opentelemetry_tracing: If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param output_array_type: The type of array `tensor` and `embedding` will be serialized to. Supports the same types as `docarray.to_protobuf(.., ndarray_type=...)`, which can be found @@ -1147,6 +1146,7 @@ def add( :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param tls: If set, connect to deployment using tls encryption + :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param upload_files: The files on the host to be uploaded to the remote workspace. This can be useful when your Deployment has more file dependencies beyond a single YAML file, e.g. diff --git a/jina/parsers/client.py b/jina/parsers/client.py index 8fb28b2c31166..e3feb84d16f96 100644 --- a/jina/parsers/client.py +++ b/jina/parsers/client.py @@ -32,10 +32,10 @@ def mixin_client_features_parser(parser): ) parser.add_argument( - '--opentelemetry-tracing', + '--tracing', action='store_true', default=False, - help='If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. ' + help='If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. ' 'Otherwise a no-op implementation will be provided.', ) @@ -54,10 +54,10 @@ def mixin_client_features_parser(parser): ) parser.add_argument( - '--opentelemetry-metrics', + '--metrics', action='store_true', default=False, - help='If set, real implementation of the metrics will be available for default monitoring and custom measurements. ' + help='If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. ' 'Otherwise a no-op implementation will be provided.', ) diff --git a/jina/parsers/orchestrate/pod.py b/jina/parsers/orchestrate/pod.py index 2b7f0b32c107b..1eaebdb26770a 100644 --- a/jina/parsers/orchestrate/pod.py +++ b/jina/parsers/orchestrate/pod.py @@ -141,10 +141,10 @@ def mixin_pod_parser(parser, pod_type: str = 'worker'): ) gp.add_argument( - '--opentelemetry-tracing', + '--tracing', action='store_true', default=False, - help='If set, real implementation of the tracer will be available and will be enabled for automatic tracing of requests and customer span creation. ' + help='If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. ' 'Otherwise a no-op implementation will be provided.', ) @@ -163,10 +163,10 @@ def mixin_pod_parser(parser, pod_type: str = 'worker'): ) parser.add_argument( - '--opentelemetry-metrics', + '--metrics', action='store_true', default=False, - help='If set, real implementation of the metrics will be available for default monitoring and custom measurements. ' + help='If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. ' 'Otherwise a no-op implementation will be provided.', ) diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index bf59b9b513e0c..8a48e0f801a88 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -80,7 +80,7 @@ def inject_dependencies( timeout_send: Optional[float] = None, metrics_registry: Optional['CollectorRegistry'] = None, runtime_name: Optional[str] = None, - opentelemetry_tracing: Optional[bool] = False, + tracing: Optional[bool] = False, tracer_provider: Optional[trace.TracerProvider] = None, grpc_tracing_server_interceptors: Optional[Sequence[Any]] = None, aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, @@ -92,13 +92,13 @@ def inject_dependencies( :param timeout_send: grpc connection timeout :param metrics_registry: metric registry when monitoring is enabled :param runtime_name: name of the runtime providing the streamer - :param opentelemetry_tracing: Enables tracing is set to True. + :param tracing: Enables tracing is set to True. :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :param grpc_tracing_server_interceptors: List of async io gprc server tracing interceptors for tracing requests. :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests if asycnio is True. :param tracing_client_interceptor: A gprc client tracing interceptor for tracing requests if asyncio is False. """ - self.opentelemetry_tracing = opentelemetry_tracing + self.tracing = tracing self.tracer_provider = tracer_provider self.grpc_tracing_server_interceptors = grpc_tracing_server_interceptors import json diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 4695755d5a402..9f163ed047047 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,6 +1,7 @@ from typing import Optional -from opentelemetry import metrics, trace +from opentelemetry import metrics as opentelmetry_metrics +from opentelemetry import trace from opentelemetry.instrumentation.grpc import ( client_interceptor as grpc_client_interceptor, ) @@ -19,18 +20,18 @@ class InstrumentationMixin: def _setup_instrumentation( self, name: str, - opentelemetry_tracing: Optional[bool] = False, + tracing: Optional[bool] = False, span_exporter_host: Optional[str] = '0.0.0.0', span_exporter_port: Optional[int] = 6831, - opentelemetry_metrics: Optional[bool] = False, + metrics: Optional[bool] = False, metrics_exporter_host: Optional[str] = '0.0.0.0', metrics_exporter_port: Optional[int] = 6831, ) -> None: - self.opentelemetry_tracing = opentelemetry_tracing - self.opentelemetry_metrics = opentelemetry_metrics + self.tracing = tracing + self.metrics = metrics - if opentelemetry_tracing: + if tracing: from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter, ) @@ -52,7 +53,7 @@ def _setup_instrumentation( self.tracer_provider = trace.NoOpTracerProvider() self.tracer = trace.NoOpTracer() - if opentelemetry_metrics: + if metrics: from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter, ) @@ -74,14 +75,14 @@ def _setup_instrumentation( self.meter_provider = meter_provider self.meter = self.meter_provider.get_meter(name) else: - self.meter_provider = metrics.NoOpMeterProvider() - self.meter = metrics.NoOpMeter(name='no-op') + self.meter_provider = opentelmetry_metrics.NoOpMeterProvider() + self.meter = opentelmetry_metrics.NoOpMeter(name='no-op') def aio_tracing_server_interceptor(self): '''Create a gRPC aio server interceptor. :returns: A service-side aio interceptor object. ''' - if self.opentelemetry_tracing: + if self.tracing: from jina.serve.instrumentation._aio_server import ( OpenTelemetryAioServerInterceptor, ) diff --git a/jina/serve/runtimes/asyncio.py b/jina/serve/runtimes/asyncio.py index 0436c83dc4a2f..cf799914664ea 100644 --- a/jina/serve/runtimes/asyncio.py +++ b/jina/serve/runtimes/asyncio.py @@ -68,10 +68,10 @@ def __init__( self._setup_monitoring() self._setup_instrumentation( name=self.args.name, - opentelemetry_tracing=self.args.opentelemetry_tracing, + tracing=self.args.tracing, span_exporter_host=self.args.span_exporter_host, span_exporter_port=self.args.span_exporter_port, - opentelemetry_metrics=self.args.opentelemetry_metrics, + metrics=self.args.metrics, metrics_exporter_host=self.args.metrics_exporter_host, metrics_exporter_port=self.args.metrics_exporter_port, ) diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 77fd9efcd8059..2207444079636 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -91,7 +91,7 @@ async def async_setup(self): timeout_send=self.timeout_send, metrics_registry=self.metrics_registry, runtime_name=self.args.name, - opentelemetry_tracing=self.opentelemetry_tracing, + tracing=self.tracing, tracer_provider=self.tracer_provider, grpc_tracing_server_interceptors=self.aio_tracing_server_interceptor(), aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( diff --git a/jina/serve/runtimes/gateway/http/app.py b/jina/serve/runtimes/gateway/http/app.py index 71d1092005078..5b0f2976c63f7 100644 --- a/jina/serve/runtimes/gateway/http/app.py +++ b/jina/serve/runtimes/gateway/http/app.py @@ -26,7 +26,7 @@ def get_fastapi_app( expose_graphql_endpoint: bool, cors: bool, logger: 'JinaLogger', - opentelemetry_tracing: Optional[bool] = None, + tracing: Optional[bool] = None, tracer_provider: Optional[trace.TracerProvider] = None, ): """ @@ -43,7 +43,7 @@ def get_fastapi_app( :param expose_graphql_endpoint: If set, /graphql endpoint is added to HTTP interface. :param cors: If set, a CORS middleware is added to FastAPI frontend to allow cross-origin access. :param logger: Jina logger. - :param opentelemetry_tracing: Enables tracing is set to True. + :param tracing: Enables tracing is set to True. :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :return: fastapi app """ @@ -65,7 +65,7 @@ def get_fastapi_app( version=__version__, ) - if opentelemetry_tracing: + if tracing: from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider) diff --git a/jina/serve/runtimes/gateway/http/gateway.py b/jina/serve/runtimes/gateway/http/gateway.py index aa6fad06f3069..eafc3f550944a 100644 --- a/jina/serve/runtimes/gateway/http/gateway.py +++ b/jina/serve/runtimes/gateway/http/gateway.py @@ -80,7 +80,7 @@ async def setup_server(self): expose_graphql_endpoint=self.expose_graphql_endpoint, cors=self.cors, logger=self.logger, - opentelemetry_tracing=self.opentelemetry_tracing, + tracing=self.tracing, tracer_provider=self.tracer_provider, ) ) diff --git a/jina/serve/runtimes/gateway/websocket/app.py b/jina/serve/runtimes/gateway/websocket/app.py index ee859f4291a45..7d609c8496ff3 100644 --- a/jina/serve/runtimes/gateway/websocket/app.py +++ b/jina/serve/runtimes/gateway/websocket/app.py @@ -26,7 +26,7 @@ def _fits_ws_close_msg(msg: str): def get_fastapi_app( streamer: 'GatewayStreamer', logger: 'JinaLogger', - opentelemetry_tracing: Optional[bool] = None, + tracing: Optional[bool] = None, tracer_provider: Optional[trace.TracerProvider] = None, ): """ @@ -34,7 +34,7 @@ def get_fastapi_app( :param streamer: gateway streamer object. :param logger: Jina logger. - :param opentelemetry_tracing: Enables tracing is set to True. + :param tracing: Enables tracing is set to True. :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :return: fastapi app """ @@ -115,7 +115,7 @@ async def send( app = FastAPI() - if opentelemetry_tracing: + if tracing: from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider) diff --git a/jina/serve/runtimes/gateway/websocket/gateway.py b/jina/serve/runtimes/gateway/websocket/gateway.py index 4b3976770c6e1..6bf8baeda9f09 100644 --- a/jina/serve/runtimes/gateway/websocket/gateway.py +++ b/jina/serve/runtimes/gateway/websocket/gateway.py @@ -49,7 +49,7 @@ async def setup_server(self): get_fastapi_app( streamer=self.streamer, logger=self.logger, - opentelemetry_tracing=self.opentelemetry_tracing, + tracing=self.tracing, tracer_provider=self.tracer_provider, ) ) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 64bdb72ba641b..7530dfa6d7887 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -62,10 +62,10 @@ '--port-monitoring', '--retries', '--floating', - '--opentelemetry-tracing', + '--tracing', '--span-exporter-host', '--span-exporter-port', - '--opentelemetry-metrics', + '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', '--install-requirements', @@ -165,10 +165,10 @@ '--port-monitoring', '--retries', '--floating', - '--opentelemetry-tracing', + '--tracing', '--span-exporter-host', '--span-exporter-port', - '--opentelemetry-metrics', + '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', ], @@ -214,7 +214,6 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], - 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -227,7 +226,6 @@ '--version', '--loglevel', 'login', - 'logout', 'deploy', 'list', 'logs', @@ -281,10 +279,10 @@ '--port-monitoring', '--retries', '--floating', - '--opentelemetry-tracing', + '--tracing', '--span-exporter-host', '--span-exporter-port', - '--opentelemetry-metrics', + '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', '--install-requirements', @@ -341,10 +339,10 @@ '--port-monitoring', '--retries', '--floating', - '--opentelemetry-tracing', + '--tracing', '--span-exporter-host', '--span-exporter-port', - '--opentelemetry-metrics', + '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', '--install-requirements', @@ -370,10 +368,10 @@ '--port', '--tls', '--asyncio', - '--opentelemetry-tracing', + '--tracing', '--span-exporter-host', '--span-exporter-port', - '--opentelemetry-metrics', + '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', '--protocol', diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index b376620232b6e..a3a6a8ee2dfd4 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -34,12 +34,12 @@ def test_grpc_gateway_instrumentation(otlp_collector, test_args): f = Flow( protocol=protocol, - opentelemetry_tracing=True, + tracing=True, span_exporter_host='localhost', span_exporter_port=4317, ).add( uses=ExecutorTestWithTracing, - opentelemetry_tracing=True, + tracing=True, span_exporter_host='localhost', span_exporter_port=4317, ) From c23f30a4261551da49b76619a457e68351c835b9 Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Thu, 6 Oct 2022 08:09:18 +0000 Subject: [PATCH 57/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 7530dfa6d7887..a8971bc23b23b 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -214,6 +214,7 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], + 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -226,6 +227,7 @@ '--version', '--loglevel', 'login', + 'logout', 'deploy', 'list', 'logs', From bcc39a88a72c3fdbc02dc1310ba18df52d3f0f32 Mon Sep 17 00:00:00 2001 From: Joan Fontanals Martinez Date: Thu, 6 Oct 2022 11:57:05 +0200 Subject: [PATCH 58/79] test: small test refactoring --- tests/integration/instrumentation/conftest.py | 2 -- .../test_flow_instrumentation.py | 19 ++++++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/integration/instrumentation/conftest.py b/tests/integration/instrumentation/conftest.py index 1a12e076f1226..c29613a9b203e 100644 --- a/tests/integration/instrumentation/conftest.py +++ b/tests/integration/instrumentation/conftest.py @@ -3,8 +3,6 @@ import pytest -from jina.logging.logger import JinaLogger - @pytest.fixture() def otlp_collector(): diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index a3a6a8ee2dfd4..d3fea172e212f 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -2,7 +2,7 @@ import pytest -from jina import Client, Flow +from jina import Flow from tests.integration.instrumentation import ( ExecutorTestWithTracing, get_services, @@ -13,24 +13,17 @@ @pytest.mark.parametrize( - 'test_args', + 'protocol, client_type, num_internal_spans', [ - {'protocol': 'grpc', 'client_type': 'GRPCClient', 'num_internal_spans': 1}, - {'protocol': 'http', 'client_type': 'HTTPClient', 'num_internal_spans': 4}, - { - 'protocol': 'websocket', - 'client_type': 'WebSocketClient', - 'num_internal_spans': 6, - }, + ('grpc', 'GRPCClient', 1), + ('http', 'HTTPClient', 4), + ('websocket', 'WebSocketClient', 6) ], ) -def test_grpc_gateway_instrumentation(otlp_collector, test_args): +def test_gateway_instrumentation(otlp_collector, protocol, client_type, num_internal_spans): import multiprocessing multiprocessing.set_start_method('spawn', force=True) - protocol = test_args['protocol'] - client_type = test_args['client_type'] - num_internal_spans = test_args['num_internal_spans'] f = Flow( protocol=protocol, From 2ce9c6750a83ea881e3b9ecac6329a0afbb0ea60 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 14:15:11 +0200 Subject: [PATCH 59/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index a8971bc23b23b..7530dfa6d7887 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -214,7 +214,6 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], - 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -227,7 +226,6 @@ '--version', '--loglevel', 'login', - 'logout', 'deploy', 'list', 'logs', From adcb457475cad5d1817a8cbb87163faef122c8b8 Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Thu, 6 Oct 2022 12:16:33 +0000 Subject: [PATCH 60/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 7530dfa6d7887..a8971bc23b23b 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -214,6 +214,7 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], + 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -226,6 +227,7 @@ '--version', '--loglevel', 'login', + 'logout', 'deploy', 'list', 'logs', From b45de43f3c961a66f53db80a1dc0bf10cb1fb5c8 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 15:59:09 +0200 Subject: [PATCH 61/79] test: dont set multiprocessing start method to spawn --- .../integration/grpc_bytes_census/test_bytes_census.py | 4 ---- .../instrumentation/test_flow_instrumentation.py | 10 ++++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/integration/grpc_bytes_census/test_bytes_census.py b/tests/integration/grpc_bytes_census/test_bytes_census.py index 3c4c59a0970ca..87aec2982be32 100644 --- a/tests/integration/grpc_bytes_census/test_bytes_census.py +++ b/tests/integration/grpc_bytes_census/test_bytes_census.py @@ -10,10 +10,6 @@ @pytest.mark.parametrize('inputs', [None, DocumentArray.empty(10)]) def test_grpc_census(inputs): - import multiprocessing - - multiprocessing.set_start_method('spawn', force=True) - assert int(os.environ.get('JINA_GRPC_SEND_BYTES', 0)) == 0 assert int(os.environ.get('JINA_GRPC_RECV_BYTES', 0)) == 0 with Flow().add().add() as f: diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index d3fea172e212f..8aa3caf27cef9 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -17,14 +17,12 @@ [ ('grpc', 'GRPCClient', 1), ('http', 'HTTPClient', 4), - ('websocket', 'WebSocketClient', 6) + ('websocket', 'WebSocketClient', 6), ], ) -def test_gateway_instrumentation(otlp_collector, protocol, client_type, num_internal_spans): - import multiprocessing - - multiprocessing.set_start_method('spawn', force=True) - +def test_gateway_instrumentation( + otlp_collector, protocol, client_type, num_internal_spans +): f = Flow( protocol=protocol, tracing=True, From bbd2fb879b58bfb4960955ad8894932dbf01e65c Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Thu, 6 Oct 2022 17:08:39 +0200 Subject: [PATCH 62/79] fix: hide opentelemetry imports --- jina/clients/base/helper.py | 17 +++++------ jina/serve/instrumentation/__init__.py | 40 +++++++++++++++----------- jina/serve/streamer.py | 2 -- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/jina/clients/base/helper.py b/jina/clients/base/helper.py index 7749e3dd8993f..385b3d55431b2 100644 --- a/jina/clients/base/helper.py +++ b/jina/clients/base/helper.py @@ -1,13 +1,9 @@ -import argparse import asyncio import random from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Optional from aiohttp import WSMsgType -from opentelemetry import trace -from opentelemetry.instrumentation.aiohttp_client import create_trace_config -from opentelemetry.propagate import inject from jina.enums import WebsocketSubProtocols from jina.importer import ImportExtensions @@ -30,7 +26,7 @@ def __init__( initial_backoff: float = 0.5, max_backoff: float = 0.1, backoff_multiplier: float = 1.5, - tracer_provider: Optional[trace.TracerProvider] = None, + tracer_provider: Optional[Any] = None, **kwargs, ) -> None: """HTTP Client to be used with the streamer @@ -48,7 +44,12 @@ def __init__( self.logger = logger self.msg_recv = 0 self.msg_sent = 0 - self._trace_config = create_trace_config(tracer_provider=tracer_provider) + if tracer_provider: + from opentelemetry.instrumentation.aiohttp_client import create_trace_config + + self._trace_config = [create_trace_config(tracer_provider=tracer_provider)] + else: + self._trace_config = None self.session = None self._session_kwargs = {} if kwargs.get('headers', None): @@ -101,7 +102,7 @@ async def start(self): import aiohttp self.session = aiohttp.ClientSession( - **self._session_kwargs, trace_configs=[self._trace_config] + **self._session_kwargs, trace_configs=self._trace_config ) await self.session.__aenter__() return self diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 9f163ed047047..7f790b0842f86 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,17 +1,4 @@ -from typing import Optional - -from opentelemetry import metrics as opentelmetry_metrics -from opentelemetry import trace -from opentelemetry.instrumentation.grpc import ( - client_interceptor as grpc_client_interceptor, -) - -from jina.serve.instrumentation._aio_client import ( - StreamStreamAioClientInterceptor, - StreamUnaryAioClientInterceptor, - UnaryStreamAioClientInterceptor, - UnaryUnaryAioClientInterceptor, -) +from typing import Any, Optional class InstrumentationMixin: @@ -32,6 +19,7 @@ def _setup_instrumentation( self.metrics = metrics if tracing: + from opentelemetry import trace from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter, ) @@ -50,10 +38,13 @@ def _setup_instrumentation( self.tracer_provider = provider self.tracer = provider.get_tracer(name) else: + from opentelemetry import trace + self.tracer_provider = trace.NoOpTracerProvider() self.tracer = trace.NoOpTracer() if metrics: + from opentelemetry import metrics as opentelmetry_metrics from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter, ) @@ -75,6 +66,8 @@ def _setup_instrumentation( self.meter_provider = meter_provider self.meter = self.meter_provider.get_meter(name) else: + from opentelemetry import metrics as opentelmetry_metrics + self.meter_provider = opentelmetry_metrics.NoOpMeterProvider() self.meter = opentelmetry_metrics.NoOpMeter(name='no-op') @@ -93,15 +86,24 @@ def aio_tracing_server_interceptor(self): @staticmethod def aio_tracing_client_interceptors( - tracer: Optional[trace.Tracer] = trace.NoOpTracer(), + tracer: Optional[Any], ): '''Create a gRPC client aio channel interceptor. :param tracer: Optional tracer that is used to instrument the client interceptors. If absent, a NoOpTracer will be used. :returns: An invocation-side list of aio interceptor objects. ''' + from opentelemetry import trace + if not tracer: tracer = trace.NoOpTracer() + from jina.serve.instrumentation._aio_client import ( + StreamStreamAioClientInterceptor, + StreamUnaryAioClientInterceptor, + UnaryStreamAioClientInterceptor, + UnaryUnaryAioClientInterceptor, + ) + return [ UnaryUnaryAioClientInterceptor(tracer), UnaryStreamAioClientInterceptor(tracer), @@ -111,12 +113,18 @@ def aio_tracing_client_interceptors( @staticmethod def tracing_client_interceptor( - tracer_provider: Optional[trace.TracerProvider] = trace.NoOpTracerProvider(), + tracer_provider: Optional[Any] = None, ): ''' :param tracer_provider: Optional tracer provider that is used to instrument the client interceptor. If absent, a NoOpTracer provider will be used. :returns: a gRPC client interceptor with the global tracing provider. ''' + from opentelemetry import trace + from opentelemetry.instrumentation.grpc import ( + client_interceptor as grpc_client_interceptor, + ) + if not tracer_provider: tracer_provider = trace.NoOpTracerProvider() + return grpc_client_interceptor(tracer_provider) diff --git a/jina/serve/streamer.py b/jina/serve/streamer.py index c3bd849251c74..46f5e8ab48a8d 100644 --- a/jina/serve/streamer.py +++ b/jina/serve/streamer.py @@ -1,10 +1,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union from docarray import DocumentArray -from opentelemetry import metrics, trace from jina.logging.logger import JinaLogger -from jina.serve.instrumentation import InstrumentationMixin from jina.serve.networking import GrpcConnectionPool from jina.serve.runtimes.gateway.graph.topology_graph import TopologyGraph from jina.serve.runtimes.gateway.request_handling import RequestHandler From dcf7296919769f14b9a2a3a97051e745d760cbdc Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 08:51:45 +0200 Subject: [PATCH 63/79] fix(runtimes): shutdown instrumentation exporters during teardown --- jina/serve/runtimes/asyncio.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jina/serve/runtimes/asyncio.py b/jina/serve/runtimes/asyncio.py index cf799914664ea..1ca6f1f79835c 100644 --- a/jina/serve/runtimes/asyncio.py +++ b/jina/serve/runtimes/asyncio.py @@ -87,8 +87,24 @@ def run_forever(self): """ self._loop.run_until_complete(self._loop_body()) + def _teardown_instrumentation(self): + try: + if self.tracing: + if hasattr(self.tracer_provider, 'force_flush'): + self.tracer_provider.force_flush() + if hasattr(self.tracer_provider, 'shutdown'): + self.tracer_provider.shutdown() + if self.metrics: + if hasattr(self.meter_provider, 'force_flush'): + self.meter_provider.force_flush() + if hasattr(self.meter_provider, 'shutdown'): + self.meter_provider.shutdown() + except Exception as ex: + self.logger.warning(f'Exception during instrumentation teardown, {str(ex)}') + def teardown(self): """Call async_teardown() and stop and close the event loop.""" + self._teardown_instrumentation() self._loop.run_until_complete(self.async_teardown()) self._loop.stop() self._loop.close() From 57be55eba55940e50a2d69030f6456f16601fd8e Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 08:53:12 +0200 Subject: [PATCH 64/79] test: spawn processes by default in tests --- tests/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 032053ade9c4e..5a38e84621fd1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -88,3 +88,8 @@ def event_loop(request): loop = asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() + + +@pytest.fixture(autouse=True) +def set_start_method(): + os.environ['JINA_MP_START_METHOD'] = 'spawn' From e9e78ae111fc39253acaae986922227670ad325a Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 12:41:46 +0200 Subject: [PATCH 65/79] fix: provide client and server interceptors only when tracing is enabled --- jina/clients/base/grpc.py | 4 +- jina/serve/instrumentation/__init__.py | 67 +++++++++++-------------- jina/serve/runtimes/gateway/__init__.py | 8 +-- 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/jina/clients/base/grpc.py b/jina/clients/base/grpc.py index 2a5d947baffe0..59379a90413b6 100644 --- a/jina/clients/base/grpc.py +++ b/jina/clients/base/grpc.py @@ -113,9 +113,7 @@ async def _get_results( options=options, asyncio=True, tls=self.args.tls, - aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( - self.tracer - ), + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors(), ) as channel: stub = jina_pb2_grpc.JinaRPCStub(channel) self.logger.debug(f'connected to {self.args.host}:{self.args.port}') diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 7f790b0842f86..361fce419bfcb 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -84,47 +84,40 @@ def aio_tracing_server_interceptor(self): else: return None - @staticmethod - def aio_tracing_client_interceptors( - tracer: Optional[Any], - ): + def aio_tracing_client_interceptors(self): '''Create a gRPC client aio channel interceptor. - :param tracer: Optional tracer that is used to instrument the client interceptors. If absent, a NoOpTracer will be used. :returns: An invocation-side list of aio interceptor objects. ''' - from opentelemetry import trace - - if not tracer: - tracer = trace.NoOpTracer() - - from jina.serve.instrumentation._aio_client import ( - StreamStreamAioClientInterceptor, - StreamUnaryAioClientInterceptor, - UnaryStreamAioClientInterceptor, - UnaryUnaryAioClientInterceptor, - ) - - return [ - UnaryUnaryAioClientInterceptor(tracer), - UnaryStreamAioClientInterceptor(tracer), - StreamUnaryAioClientInterceptor(tracer), - StreamStreamAioClientInterceptor(tracer), - ] - - @staticmethod - def tracing_client_interceptor( - tracer_provider: Optional[Any] = None, - ): + + if self.tracing: + from opentelemetry import trace + + from jina.serve.instrumentation._aio_client import ( + StreamStreamAioClientInterceptor, + StreamUnaryAioClientInterceptor, + UnaryStreamAioClientInterceptor, + UnaryUnaryAioClientInterceptor, + ) + + return [ + UnaryUnaryAioClientInterceptor(self.tracer), + UnaryStreamAioClientInterceptor(self.tracer), + StreamUnaryAioClientInterceptor(self.tracer), + StreamStreamAioClientInterceptor(self.tracer), + ] + else: + return None + + def tracing_client_interceptor(self): ''' - :param tracer_provider: Optional tracer provider that is used to instrument the client interceptor. If absent, a NoOpTracer provider will be used. :returns: a gRPC client interceptor with the global tracing provider. ''' - from opentelemetry import trace - from opentelemetry.instrumentation.grpc import ( - client_interceptor as grpc_client_interceptor, - ) - - if not tracer_provider: - tracer_provider = trace.NoOpTracerProvider() + if self.tracing: + from opentelemetry import trace + from opentelemetry.instrumentation.grpc import ( + client_interceptor as grpc_client_interceptor, + ) - return grpc_client_interceptor(tracer_provider) + return grpc_client_interceptor(self.tracer_provider) + else: + return None diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 2207444079636..785dee7a9ccbf 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -94,12 +94,8 @@ async def async_setup(self): tracing=self.tracing, tracer_provider=self.tracer_provider, grpc_tracing_server_interceptors=self.aio_tracing_server_interceptor(), - aio_tracing_client_interceptors=self.aio_tracing_client_interceptors( - self.tracer - ), - tracing_client_interceptor=self.tracing_client_interceptor( - self.tracer_provider - ), + aio_tracing_client_interceptors=self.aio_tracing_client_interceptors(), + tracing_client_interceptor=self.tracing_client_interceptor(), ) await self.gateway.setup_server() From 4f83c472ba3cdf64c8a7e78914a0fb675c800d68 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 13:32:05 +0200 Subject: [PATCH 66/79] fix(serve): correctly handle default instrumentation runtime_args --- jina/serve/executors/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index cccf170bd3fa4..600d3f4999268 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -184,14 +184,18 @@ def _init_monitoring(self): def _init_instrumentation(self, _runtime_args: Dict = {}): instrumentating_module_name = _runtime_args.get('name', self.__class__.__name__) - self.tracer_provider = _runtime_args.get( - 'tracer_provider', trace.NoOpTracerProvider() - ) - + args_tracer_provider = _runtime_args.get('tracer_provider', None) + if args_tracer_provider: + self.tracer_provider = args_tracer_provider + else: + self.tracer_provider = trace.NoOpTracerProvider() self.tracer = self.tracer_provider.get_tracer(instrumentating_module_name) - self.meter_provider = _runtime_args.get( - 'meter_provider', metrics.NoOpMeterProvider() - ) + + args_meter_provider = _runtime_args.get('meter_provider', None) + if args_meter_provider: + self.meter_provider = args_meter_provider + else: + self.meter_provider = metrics.NoOpMeterProvider() self.meter = self.meter_provider.get_meter(instrumentating_module_name) def _add_requests(self, _requests: Optional[Dict]): From a9d5b1b1dc8cc221477ad460ba4a6615f33adaa2 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 14:09:05 +0200 Subject: [PATCH 67/79] chore: hide opentelemetry imports under TYPE_CHECKING --- jina/clients/base/helper.py | 4 +++- jina/serve/gateway.py | 5 ++--- jina/serve/runtimes/gateway/http/app.py | 6 +++--- jina/serve/runtimes/gateway/websocket/app.py | 6 +++--- .../runtimes/request_handlers/data_request_handler.py | 10 +++++----- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/jina/clients/base/helper.py b/jina/clients/base/helper.py index 385b3d55431b2..766df05ecb030 100644 --- a/jina/clients/base/helper.py +++ b/jina/clients/base/helper.py @@ -12,6 +12,8 @@ from jina.types.request.status import StatusMessage if TYPE_CHECKING: + from opentelemetry import trace + from jina.logging.logger import JinaLogger @@ -26,7 +28,7 @@ def __init__( initial_backoff: float = 0.5, max_backoff: float = 0.1, backoff_multiplier: float = 1.5, - tracer_provider: Optional[Any] = None, + tracer_provider: Optional['trace.TraceProvider'] = None, **kwargs, ) -> None: """HTTP Client to be used with the streamer diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index 8a48e0f801a88..952f97d657dc6 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -4,8 +4,6 @@ import inspect from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence -from opentelemetry import trace - from jina.helper import convert_tuple_to_list from jina.jaml import JAMLCompatible from jina.logging.logger import JinaLogger @@ -15,6 +13,7 @@ __all__ = ['BaseGateway'] if TYPE_CHECKING: + from opentelemetry import trace from prometheus_client import CollectorRegistry @@ -81,7 +80,7 @@ def inject_dependencies( metrics_registry: Optional['CollectorRegistry'] = None, runtime_name: Optional[str] = None, tracing: Optional[bool] = False, - tracer_provider: Optional[trace.TracerProvider] = None, + tracer_provider: Optional['trace.TracerProvider'] = None, grpc_tracing_server_interceptors: Optional[Sequence[Any]] = None, aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, tracing_client_interceptor: Optional[Any] = None, diff --git a/jina/serve/runtimes/gateway/http/app.py b/jina/serve/runtimes/gateway/http/app.py index 5b0f2976c63f7..76a742bed2233 100644 --- a/jina/serve/runtimes/gateway/http/app.py +++ b/jina/serve/runtimes/gateway/http/app.py @@ -2,8 +2,6 @@ import json from typing import TYPE_CHECKING, Dict, List, Optional -from opentelemetry import trace - from jina import __version__ from jina.clients.request import request_generator from jina.enums import DataInputType @@ -13,6 +11,8 @@ from jina.logging.logger import JinaLogger if TYPE_CHECKING: + from opentelemetry import trace + from jina.serve.streamer import GatewayStreamer @@ -27,7 +27,7 @@ def get_fastapi_app( cors: bool, logger: 'JinaLogger', tracing: Optional[bool] = None, - tracer_provider: Optional[trace.TracerProvider] = None, + tracer_provider: Optional['trace.TracerProvider'] = None, ): """ Get the app from FastAPI as the REST interface. diff --git a/jina/serve/runtimes/gateway/websocket/app.py b/jina/serve/runtimes/gateway/websocket/app.py index 7d609c8496ff3..1e69071e9dcc6 100644 --- a/jina/serve/runtimes/gateway/websocket/app.py +++ b/jina/serve/runtimes/gateway/websocket/app.py @@ -1,8 +1,6 @@ import argparse from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Union -from opentelemetry import trace - from jina.clients.request import request_generator from jina.enums import DataInputType, WebsocketSubProtocols from jina.excepts import InternalNetworkError @@ -13,6 +11,8 @@ from jina.types.request.status import StatusMessage if TYPE_CHECKING: + from opentelemetry import trace + from jina.serve.streamer import GatewayStreamer @@ -27,7 +27,7 @@ def get_fastapi_app( streamer: 'GatewayStreamer', logger: 'JinaLogger', tracing: Optional[bool] = None, - tracer_provider: Optional[trace.TracerProvider] = None, + tracer_provider: Optional['trace.TracerProvider'] = None, ): """ Get the app from FastAPI as the Websocket interface. diff --git a/jina/serve/runtimes/request_handlers/data_request_handler.py b/jina/serve/runtimes/request_handlers/data_request_handler.py index 1a65e7f554e70..4d48fc992bd37 100644 --- a/jina/serve/runtimes/request_handlers/data_request_handler.py +++ b/jina/serve/runtimes/request_handlers/data_request_handler.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, Dict, List, Optional from docarray import DocumentArray -from opentelemetry import metrics, trace from opentelemetry.context.context import Context from jina import __default_endpoint__ @@ -13,6 +12,7 @@ if TYPE_CHECKING: import argparse + from opentelemetry import metrics, trace from prometheus_client import CollectorRegistry from jina.logging.logger import JinaLogger @@ -28,8 +28,8 @@ def __init__( args: 'argparse.Namespace', logger: 'JinaLogger', metrics_registry: Optional['CollectorRegistry'] = None, - tracer_provider: Optional[trace.TracerProvider] = None, - meter_provider: Optional[metrics.MeterProvider] = None, + tracer_provider: Optional['trace.TracerProvider'] = None, + meter_provider: Optional['metrics.MeterProvider'] = None, **kwargs, ): """Initialize private parameters and execute private loading functions. @@ -97,8 +97,8 @@ def _init_monitoring(self, metrics_registry: Optional['CollectorRegistry'] = Non def _load_executor( self, metrics_registry: Optional['CollectorRegistry'] = None, - tracer_provider: Optional[trace.TracerProvider] = None, - meter_provider: Optional[metrics.MeterProvider] = None, + tracer_provider: Optional['trace.TracerProvider'] = None, + meter_provider: Optional['metrics.MeterProvider'] = None, ): """ Load the executor to this runtime, specified by ``uses`` CLI argument. From a706480943255b417abca2391c62148035a533d2 Mon Sep 17 00:00:00 2001 From: Joan Fontanals Martinez Date: Fri, 7 Oct 2022 13:29:16 +0200 Subject: [PATCH 68/79] test: avoid using spawn --- tests/conftest.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5a38e84621fd1..021139385f22b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -89,7 +89,3 @@ def event_loop(request): yield loop loop.close() - -@pytest.fixture(autouse=True) -def set_start_method(): - os.environ['JINA_MP_START_METHOD'] = 'spawn' From ef4a23237c791aa163f8b277c312affd4824a3d2 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 17:32:36 +0200 Subject: [PATCH 69/79] fix: add explicit type info and hide imports --- jina/__init__.py | 1 - jina/clients/base/__init__.py | 2 +- jina/clients/base/grpc.py | 1 - jina/clients/base/helper.py | 2 +- jina/serve/executors/__init__.py | 8 ++--- jina/serve/gateway.py | 17 ++++++---- jina/serve/instrumentation/__init__.py | 19 +++++++---- jina/serve/networking.py | 32 ++++++++++++------- jina/serve/runtimes/gateway/grpc/gateway.py | 5 ++- jina/serve/runtimes/worker/__init__.py | 10 ++++-- jina/serve/streamer.py | 10 ++++-- tests/integration/instrumentation/__init__.py | 11 ++----- 12 files changed, 66 insertions(+), 52 deletions(-) diff --git a/jina/__init__.py b/jina/__init__.py index ac0e69a38ce10..d14f02efd790c 100644 --- a/jina/__init__.py +++ b/jina/__init__.py @@ -102,7 +102,6 @@ def _warning_on_one_line(message, category, filename, lineno, *args, **kwargs): 'JINA_OPTOUT_TELEMETRY', 'JINA_RANDOM_PORT_MAX', 'JINA_RANDOM_PORT_MIN', - 'JINA_ENABLE_OTEL_TRACING', ) __default_host__ = _os.environ.get( diff --git a/jina/clients/base/__init__.py b/jina/clients/base/__init__.py index c075501135947..ff5e5e5396c96 100644 --- a/jina/clients/base/__init__.py +++ b/jina/clients/base/__init__.py @@ -47,7 +47,6 @@ def __init__( os.unsetenv('http_proxy') os.unsetenv('https_proxy') self._inputs = None - send_telemetry_event(event='start', obj=self) self._setup_instrumentation( name=self.args.name if hasattr(self.args, 'name') @@ -59,6 +58,7 @@ def __init__( metrics_exporter_host=self.args.metrics_exporter_host, metrics_exporter_port=self.args.metrics_exporter_port, ) + send_telemetry_event(event='start', obj=self) @staticmethod def check_input(inputs: Optional['InputType'] = None, **kwargs) -> None: diff --git a/jina/clients/base/grpc.py b/jina/clients/base/grpc.py index 8f41765ccf48d..40e687999ed75 100644 --- a/jina/clients/base/grpc.py +++ b/jina/clients/base/grpc.py @@ -1,6 +1,5 @@ import asyncio import json -from cgitb import enable from typing import TYPE_CHECKING, Optional import grpc diff --git a/jina/clients/base/helper.py b/jina/clients/base/helper.py index 766df05ecb030..b408ddcc60045 100644 --- a/jina/clients/base/helper.py +++ b/jina/clients/base/helper.py @@ -1,7 +1,7 @@ import asyncio import random from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Optional from aiohttp import WSMsgType diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 600d3f4999268..05cf018456c21 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -7,16 +7,14 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union -from opentelemetry import metrics, trace - from jina import __args_executor_init__, __cache_path__, __default_endpoint__ from jina.enums import BetterEnum from jina.helper import ArgNamespace, T, iscoroutinefunction, typename from jina.importer import ImportExtensions from jina.jaml import JAML, JAMLCompatible, env_var_regex, internal_var_regex from jina.logging.logger import JinaLogger -from jina.serve.executors.decorators import avoid_concurrent_lock_cls, requests -from jina.serve.executors.metas import get_default_metas, get_executor_taboo +from jina.serve.executors.decorators import avoid_concurrent_lock_cls +from jina.serve.executors.metas import get_executor_taboo from jina.serve.helper import store_init_kwargs, wrap_func if TYPE_CHECKING: @@ -182,6 +180,8 @@ def _init_monitoring(self): self._metrics_buffer = None def _init_instrumentation(self, _runtime_args: Dict = {}): + from opentelemetry import metrics, trace + instrumentating_module_name = _runtime_args.get('name', self.__class__.__name__) args_tracer_provider = _runtime_args.get('tracer_provider', None) diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index 952f97d657dc6..e552951b3d67b 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -1,10 +1,7 @@ import abc import argparse -import functools -import inspect -from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence -from jina.helper import convert_tuple_to_list from jina.jaml import JAMLCompatible from jina.logging.logger import JinaLogger from jina.serve.helper import store_init_kwargs, wrap_func @@ -13,7 +10,11 @@ __all__ = ['BaseGateway'] if TYPE_CHECKING: + from grpc.aio._interceptor import ClientInterceptor, ServerInterceptor from opentelemetry import trace + from opentelemetry.instrumentation.grpc._client import ( + OpenTelemetryClientInterceptor, + ) from prometheus_client import CollectorRegistry @@ -81,9 +82,11 @@ def inject_dependencies( runtime_name: Optional[str] = None, tracing: Optional[bool] = False, tracer_provider: Optional['trace.TracerProvider'] = None, - grpc_tracing_server_interceptors: Optional[Sequence[Any]] = None, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, - tracing_client_interceptor: Optional[Any] = None, + grpc_tracing_server_interceptors: Optional[ + Sequence['ServerInterceptor'] + ] = None, + aio_tracing_client_interceptors: Optional[Sequence['ClientInterceptor']] = None, + tracing_client_interceptor: Optional['OpenTelemetryClientInterceptor'] = None, ): """ Set streamer object by providing runtime parameters. diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 361fce419bfcb..c62adc6591647 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -1,4 +1,10 @@ -from typing import Any, Optional +from typing import TYPE_CHECKING, Optional, Sequence + +if TYPE_CHECKING: + from grpc.aio._interceptor import ClientInterceptor, ServerInterceptor + from opentelemetry.instrumentation.grpc._client import ( + OpenTelemetryClientInterceptor, + ) class InstrumentationMixin: @@ -71,7 +77,7 @@ def _setup_instrumentation( self.meter_provider = opentelmetry_metrics.NoOpMeterProvider() self.meter = opentelmetry_metrics.NoOpMeter(name='no-op') - def aio_tracing_server_interceptor(self): + def aio_tracing_server_interceptor(self) -> Optional[Sequence['ServerInterceptor']]: '''Create a gRPC aio server interceptor. :returns: A service-side aio interceptor object. ''' @@ -84,14 +90,14 @@ def aio_tracing_server_interceptor(self): else: return None - def aio_tracing_client_interceptors(self): + def aio_tracing_client_interceptors( + self, + ) -> Optional[Sequence['ClientInterceptor']]: '''Create a gRPC client aio channel interceptor. :returns: An invocation-side list of aio interceptor objects. ''' if self.tracing: - from opentelemetry import trace - from jina.serve.instrumentation._aio_client import ( StreamStreamAioClientInterceptor, StreamUnaryAioClientInterceptor, @@ -108,12 +114,11 @@ def aio_tracing_client_interceptors(self): else: return None - def tracing_client_interceptor(self): + def tracing_client_interceptor(self) -> Optional['OpenTelemetryClientInterceptor']: ''' :returns: a gRPC client interceptor with the global tracing provider. ''' if self.tracing: - from opentelemetry import trace from opentelemetry.instrumentation.grpc import ( client_interceptor as grpc_client_interceptor, ) diff --git a/jina/serve/networking.py b/jina/serve/networking.py index 173b8a63a5ef7..429d69dbb6c81 100644 --- a/jina/serve/networking.py +++ b/jina/serve/networking.py @@ -3,7 +3,7 @@ import os from collections import defaultdict from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union +from typing import Dict, List, Optional, Sequence, Set, Tuple, Union from urllib.parse import urlparse import grpc @@ -11,7 +11,6 @@ from grpc_health.v1 import health_pb2, health_pb2_grpc from grpc_reflection.v1alpha.reflection_pb2 import ServerReflectionRequest from grpc_reflection.v1alpha.reflection_pb2_grpc import ServerReflectionStub -from opentelemetry.instrumentation.grpc.grpcext import intercept_channel from jina import __default_endpoint__ from jina.enums import PollingType @@ -20,7 +19,6 @@ from jina.logging.logger import JinaLogger from jina.proto import jina_pb2, jina_pb2_grpc from jina.serve.helper import _get_summary_time_context_or_null -from jina.serve.instrumentation import InstrumentationMixin from jina.types.request import Request from jina.types.request.data import DataRequest @@ -29,6 +27,10 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from grpc.aio._interceptor import ClientInterceptor + from opentelemetry.instrumentation.grpc._client import ( + OpenTelemetryClientInterceptor, + ) from prometheus_client import CollectorRegistry, Summary @@ -60,8 +62,8 @@ def __init__( metrics: _NetworkingMetrics, logger, runtine_name: str, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, - tracing_client_interceptor: Optional[Any] = None, + aio_tracing_client_interceptors: Optional[Sequence['ClientInterceptor']] = None, + tracing_client_interceptor: Optional['OpenTelemetryClientInterceptor'] = None, ): self.runtime_name = runtine_name self._connections = [] @@ -426,8 +428,12 @@ def __init__( runtime_name: str, logger: Optional[JinaLogger], metrics: _NetworkingMetrics, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, - tracing_client_interceptor: Optional[Any] = None, + aio_tracing_client_interceptors: Optional[ + Sequence['ClientInterceptor'] + ] = None, + tracing_client_interceptor: Optional[ + 'OpenTelemetryClientInterceptor' + ] = None, ): self._logger = logger # this maps deployments to shards or heads @@ -593,8 +599,8 @@ def __init__( logger: Optional[JinaLogger] = None, compression: Optional[str] = None, metrics_registry: Optional['CollectorRegistry'] = None, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, - tracing_client_interceptor: Optional[Any] = None, + aio_tracing_client_interceptors: Optional[Sequence['ClientInterceptor']] = None, + tracing_client_interceptor: Optional['OpenTelemetryClientInterceptor'] = None, ): self._logger = logger or JinaLogger(self.__class__.__name__) @@ -1045,6 +1051,8 @@ def __channel_with_tracing_interceptor( channel = grpc.insecure_channel(address, options=options) if interceptor: + from opentelemetry.instrumentation.grpc.grpcext import intercept_channel + return intercept_channel( channel, interceptor, @@ -1059,8 +1067,8 @@ def get_grpc_channel( asyncio: bool = False, tls: bool = False, root_certificates: Optional[str] = None, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, - tracing_client_interceptor: Optional[Any] = None, + aio_tracing_client_interceptors: Optional[Sequence['ClientInterceptor']] = None, + tracing_client_interceptor: Optional['OpenTelemetryClientInterceptor'] = None, ) -> grpc.Channel: """ Creates a grpc channel to the given address @@ -1269,7 +1277,7 @@ def create_async_channel_stub( metrics: _NetworkingMetrics, tls=False, root_certificates: Optional[str] = None, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, + aio_tracing_client_interceptors: Optional[Sequence['ClientInterceptor']] = None, ) -> Tuple[ConnectionStubs, grpc.aio.Channel]: """ Creates an async GRPC Channel. This channel has to be closed eventually! diff --git a/jina/serve/runtimes/gateway/grpc/gateway.py b/jina/serve/runtimes/gateway/grpc/gateway.py index 62dcba75e5faa..6420736cb4016 100644 --- a/jina/serve/runtimes/gateway/grpc/gateway.py +++ b/jina/serve/runtimes/gateway/grpc/gateway.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Sequence +from typing import Optional import grpc from grpc_health.v1 import health, health_pb2, health_pb2_grpc @@ -8,10 +8,9 @@ from jina.helper import get_full_version from jina.proto import jina_pb2, jina_pb2_grpc from jina.serve.gateway import BaseGateway +from jina.serve.runtimes.helper import _get_grpc_server_options from jina.types.request.status import StatusMessage -from ...helper import _get_grpc_server_options - class GRPCGateway(BaseGateway): """GRPC Gateway implementation""" diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index 42d8c0afa3368..71fc1d85bc789 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -1,12 +1,11 @@ import argparse import contextlib from abc import ABC -from typing import List +from typing import TYPE_CHECKING, List import grpc from grpc_health.v1 import health, health_pb2, health_pb2_grpc from grpc_reflection.v1alpha import reflection -from opentelemetry.propagate import Context, extract from jina.excepts import RuntimeTerminated from jina.helper import get_full_version @@ -17,6 +16,9 @@ from jina.serve.runtimes.request_handlers.data_request_handler import DataRequestHandler from jina.types.request.data import DataRequest +if TYPE_CHECKING: + from opentelemetry.propagate import Context + class WorkerRuntime(AsyncNewLoopRuntime, ABC): """Runtime procedure leveraging :class:`Grpclet` for sending DataRequests""" @@ -172,7 +174,9 @@ async def endpoint_discovery(self, empty, context) -> jina_pb2.EndpointsProto: return endpointsProto @staticmethod - def _extract_tracing_context(metadata: grpc.aio.Metadata) -> Context: + def _extract_tracing_context(metadata: grpc.aio.Metadata) -> 'Context': + from opentelemetry.propagate import extract + context = extract(dict(metadata)) return context diff --git a/jina/serve/streamer.py b/jina/serve/streamer.py index 46f5e8ab48a8d..8e4cbd7133546 100644 --- a/jina/serve/streamer.py +++ b/jina/serve/streamer.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union from docarray import DocumentArray @@ -11,6 +11,10 @@ __all__ = ['GatewayStreamer'] if TYPE_CHECKING: + from grpc.aio._interceptor import ClientInterceptor + from opentelemetry.instrumentation.grpc._client import ( + OpenTelemetryClientInterceptor, + ) from prometheus_client import CollectorRegistry @@ -32,8 +36,8 @@ def __init__( prefetch: int = 0, logger: Optional['JinaLogger'] = None, metrics_registry: Optional['CollectorRegistry'] = None, - aio_tracing_client_interceptors: Optional[Sequence[Any]] = None, - tracing_client_interceptor: Optional[Any] = None, + aio_tracing_client_interceptors: Optional[Sequence['ClientInterceptor']] = None, + tracing_client_interceptor: Optional['OpenTelemetryClientInterceptor'] = None, ): """ :param graph_representation: A dictionary describing the topology of the Deployments. 2 special nodes are expected, the name `start-gateway` and `end-gateway` to diff --git a/tests/integration/instrumentation/__init__.py b/tests/integration/instrumentation/__init__.py index a274522b641e5..0b7e04db8bb2a 100644 --- a/tests/integration/instrumentation/__init__.py +++ b/tests/integration/instrumentation/__init__.py @@ -50,15 +50,8 @@ def partition_spans_by_kind(traces): class ExecutorTestWithTracing(Executor): - def __init__( - self, - metas: Optional[Dict] = None, - requests: Optional[Dict] = None, - runtime_args: Optional[Dict] = None, - workspace: Optional[str] = None, - **kwargs, - ): - super().__init__(metas, requests, runtime_args, workspace, **kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.docs_counter = self.meter.create_counter(name='docs_counter') @requests(on='/index') From 1c0aedd36a6cda5477c27b24698e3542c6a55277 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 17:44:14 +0200 Subject: [PATCH 70/79] fix(executors): handle optional runtime_args correctly --- jina/serve/executors/__init__.py | 13 ++++++++----- jina/serve/gateway.py | 2 +- jina/serve/runtimes/gateway/http/app.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 05cf018456c21..29c6b0938a34f 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -133,7 +133,7 @@ def __init__( self._add_requests(requests) self._add_runtime_args(runtime_args) self._init_monitoring() - self._init_instrumentation(runtime_args or {}) + self._init_instrumentation(runtime_args) self._init_workspace = workspace self.logger = JinaLogger(self.__class__.__name__) if __dry_run_endpoint__ not in self.requests: @@ -179,24 +179,27 @@ def _init_monitoring(self): self._summary_method = None self._metrics_buffer = None - def _init_instrumentation(self, _runtime_args: Dict = {}): + def _init_instrumentation(self, _runtime_args: Optional[Dict] = None): from opentelemetry import metrics, trace - instrumentating_module_name = _runtime_args.get('name', self.__class__.__name__) + if not _runtime_args: + _runtime_args = {} + + instrumenting_module_name = _runtime_args.get('name', self.__class__.__name__) args_tracer_provider = _runtime_args.get('tracer_provider', None) if args_tracer_provider: self.tracer_provider = args_tracer_provider else: self.tracer_provider = trace.NoOpTracerProvider() - self.tracer = self.tracer_provider.get_tracer(instrumentating_module_name) + self.tracer = self.tracer_provider.get_tracer(instrumenting_module_name) args_meter_provider = _runtime_args.get('meter_provider', None) if args_meter_provider: self.meter_provider = args_meter_provider else: self.meter_provider = metrics.NoOpMeterProvider() - self.meter = self.meter_provider.get_meter(instrumentating_module_name) + self.meter = self.meter_provider.get_meter(instrumenting_module_name) def _add_requests(self, _requests: Optional[Dict]): if not hasattr(self, 'requests'): diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index e552951b3d67b..c045f460aeb46 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -89,7 +89,7 @@ def inject_dependencies( tracing_client_interceptor: Optional['OpenTelemetryClientInterceptor'] = None, ): """ - Set streamer object by providing runtime parameters. + Set additional dependencies by providing runtime parameters. :param args: runtime args :param timeout_send: grpc connection timeout :param metrics_registry: metric registry when monitoring is enabled diff --git a/jina/serve/runtimes/gateway/http/app.py b/jina/serve/runtimes/gateway/http/app.py index 76a742bed2233..679a91151b7c2 100644 --- a/jina/serve/runtimes/gateway/http/app.py +++ b/jina/serve/runtimes/gateway/http/app.py @@ -43,7 +43,7 @@ def get_fastapi_app( :param expose_graphql_endpoint: If set, /graphql endpoint is added to HTTP interface. :param cors: If set, a CORS middleware is added to FastAPI frontend to allow cross-origin access. :param logger: Jina logger. - :param tracing: Enables tracing is set to True. + :param tracing: Enables tracing if set to True. :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :return: fastapi app """ From c292234710cbcccaa8cf1fd8ec8421079f1e5bcb Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Fri, 7 Oct 2022 17:46:47 +0200 Subject: [PATCH 71/79] chore: rename otel_context to tracing_context --- .../serve/runtimes/request_handlers/data_request_handler.py | 6 +++--- jina/serve/runtimes/worker/__init__.py | 4 ++-- tests/integration/instrumentation/__init__.py | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/jina/serve/runtimes/request_handlers/data_request_handler.py b/jina/serve/runtimes/request_handlers/data_request_handler.py index 4d48fc992bd37..6007b6a0ac717 100644 --- a/jina/serve/runtimes/request_handlers/data_request_handler.py +++ b/jina/serve/runtimes/request_handlers/data_request_handler.py @@ -147,12 +147,12 @@ def _parse_params(parameters: Dict, executor_name: str): return parsed_params async def handle( - self, requests: List['DataRequest'], otel_context: Context = None + self, requests: List['DataRequest'], tracing_context: Context = None ) -> DataRequest: """Initialize private parameters and execute private loading functions. :param requests: The messages to handle containing a DataRequest - :param otel_context: OpenTelemetry Context from the originating request. + :param tracing_context: OpenTelemetry tracing context from the originating request. :returns: the processed message """ # skip executor if endpoints mismatch @@ -190,7 +190,7 @@ async def handle( requests, field='docs', ), - otel_context=otel_context, + tracing_context=tracing_context, ) # assigning result back to request if return_data is not None: diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index 71fc1d85bc789..e0de13dc15998 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -194,11 +194,11 @@ async def process_data(self, requests: List[DataRequest], context) -> DataReques if self.logger.debug_enabled: self._log_data_request(requests[0]) - otel_context = WorkerRuntime._extract_tracing_context( + tracing_context = WorkerRuntime._extract_tracing_context( context.invocation_metadata() ) result = await self._data_request_handler.handle( - requests=requests, otel_context=otel_context + requests=requests, tracing_context=tracing_context ) if self._successful_requests_metrics: self._successful_requests_metrics.inc() diff --git a/tests/integration/instrumentation/__init__.py b/tests/integration/instrumentation/__init__.py index 0b7e04db8bb2a..c5b1e8c542698 100644 --- a/tests/integration/instrumentation/__init__.py +++ b/tests/integration/instrumentation/__init__.py @@ -55,8 +55,10 @@ def __init__(self, *args, **kwargs): self.docs_counter = self.meter.create_counter(name='docs_counter') @requests(on='/index') - def empty(self, docs: 'DocumentArray', otel_context: Context, **kwargs): - with self.tracer.start_as_current_span('dummy', context=otel_context) as span: + def empty(self, docs: 'DocumentArray', tracing_context: Context, **kwargs): + with self.tracer.start_as_current_span( + 'dummy', context=tracing_context + ) as span: span.set_attribute('len_docs', len(docs)) self.docs_counter.add(len(docs)) return docs From 01d543bf51e4b5dc7df631935f489c00aeca1d69 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 10 Oct 2022 10:13:21 +0200 Subject: [PATCH 72/79] feat: use None instead of NoOp tracer and meter implementations --- jina/serve/executors/__init__.py | 12 ++++++------ jina/serve/instrumentation/__init__.py | 14 ++++---------- jina/serve/runtimes/asyncio.py | 4 ++-- tests/integration/instrumentation/__init__.py | 2 -- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index 29c6b0938a34f..daac6b557f944 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -180,8 +180,6 @@ def _init_monitoring(self): self._metrics_buffer = None def _init_instrumentation(self, _runtime_args: Optional[Dict] = None): - from opentelemetry import metrics, trace - if not _runtime_args: _runtime_args = {} @@ -190,16 +188,18 @@ def _init_instrumentation(self, _runtime_args: Optional[Dict] = None): args_tracer_provider = _runtime_args.get('tracer_provider', None) if args_tracer_provider: self.tracer_provider = args_tracer_provider + self.tracer = self.tracer_provider.get_tracer(instrumenting_module_name) else: - self.tracer_provider = trace.NoOpTracerProvider() - self.tracer = self.tracer_provider.get_tracer(instrumenting_module_name) + self.tracer_provider = None + self.tracer = None args_meter_provider = _runtime_args.get('meter_provider', None) if args_meter_provider: self.meter_provider = args_meter_provider + self.meter = self.meter_provider.get_meter(instrumenting_module_name) else: - self.meter_provider = metrics.NoOpMeterProvider() - self.meter = self.meter_provider.get_meter(instrumenting_module_name) + self.meter_provider = None + self.meter = None def _add_requests(self, _requests: Optional[Dict]): if not hasattr(self, 'requests'): diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index c62adc6591647..3d2674902c168 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -25,7 +25,6 @@ def _setup_instrumentation( self.metrics = metrics if tracing: - from opentelemetry import trace from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter, ) @@ -44,13 +43,10 @@ def _setup_instrumentation( self.tracer_provider = provider self.tracer = provider.get_tracer(name) else: - from opentelemetry import trace - - self.tracer_provider = trace.NoOpTracerProvider() - self.tracer = trace.NoOpTracer() + self.tracer_provider = None + self.tracer = None if metrics: - from opentelemetry import metrics as opentelmetry_metrics from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter, ) @@ -72,10 +68,8 @@ def _setup_instrumentation( self.meter_provider = meter_provider self.meter = self.meter_provider.get_meter(name) else: - from opentelemetry import metrics as opentelmetry_metrics - - self.meter_provider = opentelmetry_metrics.NoOpMeterProvider() - self.meter = opentelmetry_metrics.NoOpMeter(name='no-op') + self.meter_provider = None + self.meter = None def aio_tracing_server_interceptor(self) -> Optional[Sequence['ServerInterceptor']]: '''Create a gRPC aio server interceptor. diff --git a/jina/serve/runtimes/asyncio.py b/jina/serve/runtimes/asyncio.py index 1ca6f1f79835c..8f3ce2526ce1e 100644 --- a/jina/serve/runtimes/asyncio.py +++ b/jina/serve/runtimes/asyncio.py @@ -89,12 +89,12 @@ def run_forever(self): def _teardown_instrumentation(self): try: - if self.tracing: + if self.tracing and self.tracer_provider: if hasattr(self.tracer_provider, 'force_flush'): self.tracer_provider.force_flush() if hasattr(self.tracer_provider, 'shutdown'): self.tracer_provider.shutdown() - if self.metrics: + if self.metrics and self.meter_provider: if hasattr(self.meter_provider, 'force_flush'): self.meter_provider.force_flush() if hasattr(self.meter_provider, 'shutdown'): diff --git a/tests/integration/instrumentation/__init__.py b/tests/integration/instrumentation/__init__.py index c5b1e8c542698..62d8e41cd94d1 100644 --- a/tests/integration/instrumentation/__init__.py +++ b/tests/integration/instrumentation/__init__.py @@ -52,7 +52,6 @@ def partition_spans_by_kind(traces): class ExecutorTestWithTracing(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.docs_counter = self.meter.create_counter(name='docs_counter') @requests(on='/index') def empty(self, docs: 'DocumentArray', tracing_context: Context, **kwargs): @@ -60,7 +59,6 @@ def empty(self, docs: 'DocumentArray', tracing_context: Context, **kwargs): 'dummy', context=tracing_context ) as span: span.set_attribute('len_docs', len(docs)) - self.docs_counter.add(len(docs)) return docs From 4afc51b5f63b88a1bcdfaaf74f70eafc4094d27a Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 10 Oct 2022 10:20:18 +0200 Subject: [PATCH 73/79] fix: remove unused import --- tests/unit/serve/executors/test_decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/serve/executors/test_decorators.py b/tests/unit/serve/executors/test_decorators.py index 237260a418c2f..ebc6660564366 100644 --- a/tests/unit/serve/executors/test_decorators.py +++ b/tests/unit/serve/executors/test_decorators.py @@ -3,9 +3,9 @@ import pytest from jina.helper import iscoroutinefunction -from jina.serve.executors import get_default_metas, get_executor_taboo +from jina.serve.executors import get_executor_taboo from jina.serve.executors.decorators import requests -from jina.serve.helper import store_init_kwargs, wrap_func +from jina.serve.helper import store_init_kwargs def test_store_init_kwargs(): From 70146e4c37fd8075f5c2e19a38c1dce25e3857b0 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 10 Oct 2022 14:32:56 +0200 Subject: [PATCH 74/79] feat: add default tracing span for DataRequestHandler handle invocation --- jina/serve/executors/__init__.py | 29 +++++++++++++++---- .../request_handlers/data_request_handler.py | 4 +-- tests/integration/instrumentation/__init__.py | 13 +++++---- .../test_flow_instrumentation.py | 6 ++-- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py index daac6b557f944..f84f820e0312a 100644 --- a/jina/serve/executors/__init__.py +++ b/jina/serve/executors/__init__.py @@ -18,6 +18,7 @@ from jina.serve.helper import store_init_kwargs, wrap_func if TYPE_CHECKING: + from opentelemetry.context.context import Context from prometheus_client import Summary __dry_run_endpoint__ = '_jina_dry_run_' @@ -315,7 +316,16 @@ async def __acall__(self, req_endpoint: str, **kwargs): elif __default_endpoint__ in self.requests: return await self.__acall_endpoint__(__default_endpoint__, **kwargs) - async def __acall_endpoint__(self, req_endpoint, **kwargs): + async def __acall_endpoint__( + self, req_endpoint, tracing_context: Optional['Context'], **kwargs + ): + async def exec_func(summary, tracing_context): + with summary: + if iscoroutinefunction(func): + return await func(self, tracing_context=tracing_context, **kwargs) + else: + return func(self, tracing_context=tracing_context, **kwargs) + func = self.requests[req_endpoint] runtime_name = ( @@ -330,11 +340,18 @@ async def __acall_endpoint__(self, req_endpoint, **kwargs): else contextlib.nullcontext() ) - with _summary: - if iscoroutinefunction(func): - return await func(self, **kwargs) - else: - return func(self, **kwargs) + if self.tracer: + with self.tracer.start_span(req_endpoint, context=tracing_context) as _: + from opentelemetry.propagate import extract + from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, + ) + + tracing_carrier_context = {} + TraceContextTextMapPropagator().inject(tracing_carrier_context) + return await exec_func(_summary, extract(tracing_carrier_context)) + else: + return await exec_func(_summary, None) @property def workspace(self) -> Optional[str]: diff --git a/jina/serve/runtimes/request_handlers/data_request_handler.py b/jina/serve/runtimes/request_handlers/data_request_handler.py index 6007b6a0ac717..35497e5eb39d4 100644 --- a/jina/serve/runtimes/request_handlers/data_request_handler.py +++ b/jina/serve/runtimes/request_handlers/data_request_handler.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, Dict, List, Optional from docarray import DocumentArray -from opentelemetry.context.context import Context from jina import __default_endpoint__ from jina.excepts import BadConfigSource @@ -13,6 +12,7 @@ import argparse from opentelemetry import metrics, trace + from opentelemetry.context.context import Context from prometheus_client import CollectorRegistry from jina.logging.logger import JinaLogger @@ -147,7 +147,7 @@ def _parse_params(parameters: Dict, executor_name: str): return parsed_params async def handle( - self, requests: List['DataRequest'], tracing_context: Context = None + self, requests: List['DataRequest'], tracing_context: 'Context' = None ) -> DataRequest: """Initialize private parameters and execute private loading functions. diff --git a/tests/integration/instrumentation/__init__.py b/tests/integration/instrumentation/__init__.py index 62d8e41cd94d1..249cef8cb777e 100644 --- a/tests/integration/instrumentation/__init__.py +++ b/tests/integration/instrumentation/__init__.py @@ -54,11 +54,14 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @requests(on='/index') - def empty(self, docs: 'DocumentArray', tracing_context: Context, **kwargs): - with self.tracer.start_as_current_span( - 'dummy', context=tracing_context - ) as span: - span.set_attribute('len_docs', len(docs)) + def empty( + self, docs: 'DocumentArray', tracing_context: Optional[Context], **kwargs + ): + if self.tracer: + with self.tracer.start_span('dummy', context=tracing_context) as span: + span.set_attribute('len_docs', len(docs)) + return docs + else: return docs diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index 8aa3caf27cef9..f464e1002de2c 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -15,9 +15,9 @@ @pytest.mark.parametrize( 'protocol, client_type, num_internal_spans', [ - ('grpc', 'GRPCClient', 1), - ('http', 'HTTPClient', 4), - ('websocket', 'WebSocketClient', 6), + ('grpc', 'GRPCClient', 2), + ('http', 'HTTPClient', 5), + ('websocket', 'WebSocketClient', 7), ], ) def test_gateway_instrumentation( From 7f20c062bc57405c883c2d8bc6e07fbe4c366b6b Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 10 Oct 2022 16:32:40 +0200 Subject: [PATCH 75/79] test: add test case to verify exception recording in a span --- tests/integration/instrumentation/__init__.py | 32 ++++++++++++++++ .../test_flow_instrumentation.py | 38 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/tests/integration/instrumentation/__init__.py b/tests/integration/instrumentation/__init__.py index 249cef8cb777e..58a5a9b709f68 100644 --- a/tests/integration/instrumentation/__init__.py +++ b/tests/integration/instrumentation/__init__.py @@ -73,3 +73,35 @@ def get_services(): response_json = response.json() services = response_json.get('data', []) or [] return [service for service in services if service != 'jaeger-query'] + + +class ExecutorFailureWithTracing(Executor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.failure_counter = 0 + + @requests(on='/index') + def empty( + self, docs: 'DocumentArray', tracing_context: Optional[Context], **kwargs + ): + if self.tracer: + with self.tracer.start_span('dummy', context=tracing_context) as span: + span.set_attribute('len_docs', len(docs)) + if not self.failure_counter: + self.failure_counter += 1 + raise NotImplementedError + else: + return docs + else: + return docs + + +def spans_with_error(spans): + error_spans = [] + for span in spans: + for tag in span['tags']: + if 'otel.status_code' == tag.get('key', '') and 'ERROR' == tag.get( + 'value', '' + ): + error_spans.append(span) + return error_spans diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index f464e1002de2c..ff2f49896ea5f 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -4,11 +4,13 @@ from jina import Flow from tests.integration.instrumentation import ( + ExecutorFailureWithTracing, ExecutorTestWithTracing, get_services, get_trace_ids, get_traces, partition_spans_by_kind, + spans_with_error, ) @@ -61,3 +63,39 @@ def test_gateway_instrumentation( trace_ids = get_trace_ids(client_traces) assert len(trace_ids) == 1 + + +def test_executor_instrumentation(otlp_collector): + f = Flow( + tracing=True, + span_exporter_host='localhost', + span_exporter_port=4317, + ).add(uses=ExecutorFailureWithTracing) + + with f: + from jina import DocumentArray + + try: + f.post( + f'/index', + DocumentArray.empty(2), + ) + except: + pass + # give some time for the tracing and metrics exporters to finish exporting. + # the client is slow to export the data + time.sleep(8) + + client_type = 'GRPCClient' + client_traces = get_traces(client_type) + (server_spans, client_spans, internal_spans) = partition_spans_by_kind( + client_traces + ) + assert len(spans_with_error(server_spans)) == 0 + assert len(spans_with_error(client_spans)) == 0 + assert len(internal_spans) == 2 + # Errors reported by DataRequestHandler and request method level spans + assert len(spans_with_error(internal_spans)) == 2 + + trace_ids = get_trace_ids(client_traces) + assert len(trace_ids) == 1 From 550a9750bb2b58549f5d665eaac0fe2b9f7e36fd Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Mon, 10 Oct 2022 16:51:55 +0200 Subject: [PATCH 76/79] fix: use continue_on_error instead of try-except-pass --- .../instrumentation/test_flow_instrumentation.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index ff2f49896ea5f..e519d9e612bcf 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -40,10 +40,7 @@ def test_gateway_instrumentation( with f: from jina import DocumentArray - f.post( - f'/index', - DocumentArray.empty(2), - ) + f.post(f'/index', DocumentArray.empty(2), continue_on_error=True) # give some time for the tracing and metrics exporters to finish exporting. # the client is slow to export the data time.sleep(8) @@ -75,13 +72,7 @@ def test_executor_instrumentation(otlp_collector): with f: from jina import DocumentArray - try: - f.post( - f'/index', - DocumentArray.empty(2), - ) - except: - pass + f.post(f'/index', DocumentArray.empty(2), continue_on_error=True) # give some time for the tracing and metrics exporters to finish exporting. # the client is slow to export the data time.sleep(8) From d55d86c24f5f6dd134155d97b15f1b3a4516df04 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 11 Oct 2022 13:24:20 +0200 Subject: [PATCH 77/79] chore: rename method name to match returning a list --- jina/serve/gateway.py | 2 +- jina/serve/instrumentation/__init__.py | 4 +++- jina/serve/runtimes/gateway/__init__.py | 2 +- jina/serve/runtimes/head/__init__.py | 2 +- jina/serve/runtimes/worker/__init__.py | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/jina/serve/gateway.py b/jina/serve/gateway.py index c045f460aeb46..45102f3dd28a8 100644 --- a/jina/serve/gateway.py +++ b/jina/serve/gateway.py @@ -94,7 +94,7 @@ def inject_dependencies( :param timeout_send: grpc connection timeout :param metrics_registry: metric registry when monitoring is enabled :param runtime_name: name of the runtime providing the streamer - :param tracing: Enables tracing is set to True. + :param tracing: Enables tracing if set to True. :param tracer_provider: If tracing is enabled the tracer_provider will be used to instrument the code. :param grpc_tracing_server_interceptors: List of async io gprc server tracing interceptors for tracing requests. :param aio_tracing_client_interceptors: List of async io gprc client tracing interceptors for tracing requests if asycnio is True. diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 3d2674902c168..9518f7f9d5395 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -71,7 +71,9 @@ def _setup_instrumentation( self.meter_provider = None self.meter = None - def aio_tracing_server_interceptor(self) -> Optional[Sequence['ServerInterceptor']]: + def aio_tracing_server_interceptors( + self, + ) -> Optional[Sequence['ServerInterceptor']]: '''Create a gRPC aio server interceptor. :returns: A service-side aio interceptor object. ''' diff --git a/jina/serve/runtimes/gateway/__init__.py b/jina/serve/runtimes/gateway/__init__.py index 785dee7a9ccbf..0ccf3e01cfe2f 100644 --- a/jina/serve/runtimes/gateway/__init__.py +++ b/jina/serve/runtimes/gateway/__init__.py @@ -93,7 +93,7 @@ async def async_setup(self): runtime_name=self.args.name, tracing=self.tracing, tracer_provider=self.tracer_provider, - grpc_tracing_server_interceptors=self.aio_tracing_server_interceptor(), + grpc_tracing_server_interceptors=self.aio_tracing_server_interceptors(), aio_tracing_client_interceptors=self.aio_tracing_client_interceptors(), tracing_client_interceptor=self.tracing_client_interceptor(), ) diff --git a/jina/serve/runtimes/head/__init__.py b/jina/serve/runtimes/head/__init__.py index 68709360120d0..5c5b286632f35 100644 --- a/jina/serve/runtimes/head/__init__.py +++ b/jina/serve/runtimes/head/__init__.py @@ -146,7 +146,7 @@ async def async_setup(self): """Wait for the GRPC server to start""" self._grpc_server = grpc.aio.server( options=_get_grpc_server_options(self.args.grpc_server_options), - interceptors=self.aio_tracing_server_interceptor(), + interceptors=self.aio_tracing_server_interceptors(), ) jina_pb2_grpc.add_JinaSingleDataRequestRPCServicer_to_server( diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py index e0de13dc15998..0864c212fa7f0 100644 --- a/jina/serve/runtimes/worker/__init__.py +++ b/jina/serve/runtimes/worker/__init__.py @@ -97,7 +97,7 @@ async def _async_setup_grpc_server(self): self._grpc_server = grpc.aio.server( options=_get_grpc_server_options(self.args.grpc_server_options), - interceptors=self.aio_tracing_server_interceptor(), + interceptors=self.aio_tracing_server_interceptors(), ) jina_pb2_grpc.add_JinaSingleDataRequestRPCServicer_to_server( From 132a9320071d9979654e28617bb4998f15942ab2 Mon Sep 17 00:00:00 2001 From: Girish Chandrashekar Date: Tue, 11 Oct 2022 14:31:31 +0200 Subject: [PATCH 78/79] fix: rename span_exporter args to traces_exporter --- docs/fundamentals/flow/executor-args.md | 4 +-- docs/fundamentals/flow/gateway-args.md | 4 +-- jina/clients/__init__.py | 12 +++---- jina/clients/base/__init__.py | 4 +-- jina/orchestrate/flow/base.py | 36 +++++++++---------- jina/parsers/client.py | 4 +-- jina/parsers/orchestrate/pod.py | 4 +-- jina/serve/instrumentation/__init__.py | 7 ++-- jina/serve/runtimes/asyncio.py | 4 +-- jina_cli/autocomplete.py | 22 ++++++------ .../test_flow_instrumentation.py | 12 +++---- 11 files changed, 56 insertions(+), 57 deletions(-) diff --git a/docs/fundamentals/flow/executor-args.md b/docs/fundamentals/flow/executor-args.md index 0e12693a9e7df..663d4c1966a56 100644 --- a/docs/fundamentals/flow/executor-args.md +++ b/docs/fundamentals/flow/executor-args.md @@ -36,8 +36,8 @@ | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | | `tracing` | If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | -| `span_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | -| `span_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | +| `traces_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | +| `traces_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | | `metrics` | If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `metrics_exporter_host` | If tracing is enabled, this hostname will be used to configure the metrics exporter agent. | `string` | `None` | | `metrics_exporter_port` | If tracing is enabled, this port will be used to configure the metrics exporter agent. | `number` | `None` | diff --git a/docs/fundamentals/flow/gateway-args.md b/docs/fundamentals/flow/gateway-args.md index 2d17e428c03f7..999129acfc4d7 100644 --- a/docs/fundamentals/flow/gateway-args.md +++ b/docs/fundamentals/flow/gateway-args.md @@ -51,8 +51,8 @@ | `retries` | Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) | `number` | `-1` | | `floating` | If set, the current Pod/Deployment can not be further chained, and the next `.add()` will chain after the last Pod/Deployment not this current one. | `boolean` | `False` | | `tracing` | If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. | `boolean` | `False` | -| `span_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | -| `span_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | +| `traces_exporter_host` | If tracing is enabled, this hostname will be used to configure the trace exporter agent. | `string` | `None` | +| `traces_exporter_port` | If tracing is enabled, this port will be used to configure the trace exporter agent. | `number` | `None` | | `metrics` | If set, the sdk implementation of the OpenTelemetry metrics will be available for default monitoring and custom measurements. Otherwise a no-op implementation will be provided. | `boolean` | `False` | | `metrics_exporter_host` | If tracing is enabled, this hostname will be used to configure the metrics exporter agent. | `string` | `None` | | `metrics_exporter_port` | If tracing is enabled, this port will be used to configure the metrics exporter agent. | `number` | `None` | \ No newline at end of file diff --git a/jina/clients/__init__.py b/jina/clients/__init__.py index 2ab002bb6b93f..c1451d94f35ec 100644 --- a/jina/clients/__init__.py +++ b/jina/clients/__init__.py @@ -26,9 +26,9 @@ def Client( port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, tls: Optional[bool] = False, + traces_exporter_host: Optional[str] = None, + traces_exporter_port: Optional[int] = None, tracing: Optional[bool] = False, **kwargs ) -> Union[ @@ -49,9 +49,9 @@ def Client( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :return: the new Client object @@ -99,9 +99,9 @@ def Client( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :return: the new Client object diff --git a/jina/clients/base/__init__.py b/jina/clients/base/__init__.py index ff5e5e5396c96..b5c3cce8a9c73 100644 --- a/jina/clients/base/__init__.py +++ b/jina/clients/base/__init__.py @@ -52,8 +52,8 @@ def __init__( if hasattr(self.args, 'name') else self.__class__.__name__, tracing=self.args.tracing, - span_exporter_host=self.args.span_exporter_host, - span_exporter_port=self.args.span_exporter_port, + traces_exporter_host=self.args.traces_exporter_host, + traces_exporter_port=self.args.traces_exporter_port, metrics=self.args.metrics, metrics_exporter_host=self.args.metrics_exporter_host, metrics_exporter_port=self.args.metrics_exporter_port, diff --git a/jina/orchestrate/flow/base.py b/jina/orchestrate/flow/base.py index dff42f25528da..109a2722e3843 100644 --- a/jina/orchestrate/flow/base.py +++ b/jina/orchestrate/flow/base.py @@ -124,9 +124,9 @@ def __init__( port: Optional[int] = None, protocol: Optional[str] = 'GRPC', proxy: Optional[bool] = False, - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, tls: Optional[bool] = False, + traces_exporter_host: Optional[str] = None, + traces_exporter_port: Optional[int] = None, tracing: Optional[bool] = False, **kwargs, ): @@ -140,9 +140,9 @@ def __init__( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. .. # noqa: DAR202 @@ -199,14 +199,14 @@ def __init__( retries: Optional[int] = -1, runtime_cls: Optional[str] = 'GatewayRuntime', shards: Optional[int] = 1, - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, ssl_certfile: Optional[str] = None, ssl_keyfile: Optional[str] = None, timeout_ctrl: Optional[int] = 60, timeout_ready: Optional[int] = 600000, timeout_send: Optional[int] = None, title: Optional[str] = None, + traces_exporter_host: Optional[str] = None, + traces_exporter_port: Optional[int] = None, tracing: Optional[bool] = False, uses: Optional[Union[str, Type['BaseExecutor'], dict]] = None, uses_with: Optional[dict] = None, @@ -296,14 +296,14 @@ def __init__( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param ssl_certfile: the path to the certificate file :param ssl_keyfile: the path to the key file :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param title: The title of this HTTP server. It will be used in automatics docs such as Swagger UI. + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param uses: The config of the gateway, it could be one of the followings: * the string literal of an Gateway class name @@ -416,9 +416,9 @@ def __init__( :param port: The port of the Gateway, which the client should connect to. :param protocol: Communication protocol between server and client. :param proxy: If set, respect the http_proxy and https_proxy environment variables. otherwise, it will unset these proxy variables before start. gRPC seems to prefer no proxy - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tls: If set, connect to gateway using tls encryption + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param compression: The compression mechanism used when sending requests from the Head to the WorkerRuntimes. For more details, check https://grpc.github.io/grpc/python/grpc.html#compression. :param cors: If set, a CORS middleware is added to FastAPI frontend to allow cross-origin access. @@ -499,14 +499,14 @@ def __init__( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param ssl_certfile: the path to the certificate file :param ssl_keyfile: the path to the key file :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param title: The title of this HTTP server. It will be used in automatics docs such as Swagger UI. + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param uses: The config of the gateway, it could be one of the followings: * the string literal of an Gateway class name @@ -907,12 +907,12 @@ def add( retries: Optional[int] = -1, runtime_cls: Optional[str] = 'WorkerRuntime', shards: Optional[int] = 1, - span_exporter_host: Optional[str] = None, - span_exporter_port: Optional[int] = None, timeout_ctrl: Optional[int] = 60, timeout_ready: Optional[int] = 600000, timeout_send: Optional[int] = None, tls: Optional[bool] = False, + traces_exporter_host: Optional[str] = None, + traces_exporter_port: Optional[int] = None, tracing: Optional[bool] = False, upload_files: Optional[List[str]] = None, uses: Optional[Union[str, Type['BaseExecutor'], dict]] = 'BaseExecutor', @@ -999,12 +999,12 @@ def add( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param tls: If set, connect to deployment using tls encryption + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param upload_files: The files on the host to be uploaded to the remote workspace. This can be useful when your Deployment has more @@ -1154,12 +1154,12 @@ def add( :param retries: Number of retries per gRPC call. If <0 it defaults to max(3, num_replicas) :param runtime_cls: The runtime class to run inside the Pod :param shards: The number of shards in the deployment running at the same time. For more details check https://docs.jina.ai/fundamentals/flow/create-flow/#complex-flow-topologies - :param span_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. - :param span_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param timeout_ctrl: The timeout in milliseconds of the control request, -1 for waiting forever :param timeout_ready: The timeout in milliseconds of a Pod waits for the runtime to be ready, -1 for waiting forever :param timeout_send: The timeout in milliseconds used when sending data requests to Executors, -1 means no timeout, disabled by default :param tls: If set, connect to deployment using tls encryption + :param traces_exporter_host: If tracing is enabled, this hostname will be used to configure the trace exporter agent. + :param traces_exporter_port: If tracing is enabled, this port will be used to configure the trace exporter agent. :param tracing: If set, the sdk implementation of the OpenTelemetry tracer will be available and will be enabled for automatic tracing of requests and customer span creation. Otherwise a no-op implementation will be provided. :param upload_files: The files on the host to be uploaded to the remote workspace. This can be useful when your Deployment has more diff --git a/jina/parsers/client.py b/jina/parsers/client.py index e3feb84d16f96..f302eec682b90 100644 --- a/jina/parsers/client.py +++ b/jina/parsers/client.py @@ -40,14 +40,14 @@ def mixin_client_features_parser(parser): ) parser.add_argument( - '--span-exporter-host', + '--traces-exporter-host', type=str, default=None, help='If tracing is enabled, this hostname will be used to configure the trace exporter agent.', ) parser.add_argument( - '--span-exporter-port', + '--traces-exporter-port', type=int, default=None, help='If tracing is enabled, this port will be used to configure the trace exporter agent.', diff --git a/jina/parsers/orchestrate/pod.py b/jina/parsers/orchestrate/pod.py index ec0de0b41e456..3e98e20593afd 100644 --- a/jina/parsers/orchestrate/pod.py +++ b/jina/parsers/orchestrate/pod.py @@ -151,14 +151,14 @@ def mixin_pod_parser(parser, pod_type: str = 'worker'): ) parser.add_argument( - '--span-exporter-host', + '--traces-exporter-host', type=str, default=None, help='If tracing is enabled, this hostname will be used to configure the trace exporter agent.', ) parser.add_argument( - '--span-exporter-port', + '--traces-exporter-port', type=int, default=None, help='If tracing is enabled, this port will be used to configure the trace exporter agent.', diff --git a/jina/serve/instrumentation/__init__.py b/jina/serve/instrumentation/__init__.py index 9518f7f9d5395..64786f3478745 100644 --- a/jina/serve/instrumentation/__init__.py +++ b/jina/serve/instrumentation/__init__.py @@ -14,8 +14,8 @@ def _setup_instrumentation( self, name: str, tracing: Optional[bool] = False, - span_exporter_host: Optional[str] = '0.0.0.0', - span_exporter_port: Optional[int] = 6831, + traces_exporter_host: Optional[str] = '0.0.0.0', + traces_exporter_port: Optional[int] = 6831, metrics: Optional[bool] = False, metrics_exporter_host: Optional[str] = '0.0.0.0', metrics_exporter_port: Optional[int] = 6831, @@ -36,7 +36,8 @@ def _setup_instrumentation( provider = TracerProvider(resource=resource) processor = BatchSpanProcessor( OTLPSpanExporter( - endpoint=f'{span_exporter_host}:{span_exporter_port}', insecure=True + endpoint=f'{traces_exporter_host}:{traces_exporter_port}', + insecure=True, ) ) provider.add_span_processor(processor) diff --git a/jina/serve/runtimes/asyncio.py b/jina/serve/runtimes/asyncio.py index 8f3ce2526ce1e..870c78fae003c 100644 --- a/jina/serve/runtimes/asyncio.py +++ b/jina/serve/runtimes/asyncio.py @@ -69,8 +69,8 @@ def __init__( self._setup_instrumentation( name=self.args.name, tracing=self.args.tracing, - span_exporter_host=self.args.span_exporter_host, - span_exporter_port=self.args.span_exporter_port, + traces_exporter_host=self.args.traces_exporter_host, + traces_exporter_port=self.args.traces_exporter_port, metrics=self.args.metrics, metrics_exporter_host=self.args.metrics_exporter_host, metrics_exporter_port=self.args.metrics_exporter_port, diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index a8971bc23b23b..118ec63a6a50a 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -63,8 +63,8 @@ '--retries', '--floating', '--tracing', - '--span-exporter-host', - '--span-exporter-port', + '--traces-exporter-host', + '--traces-exporter-port', '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', @@ -166,8 +166,8 @@ '--retries', '--floating', '--tracing', - '--span-exporter-host', - '--span-exporter-port', + '--traces-exporter-host', + '--traces-exporter-port', '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', @@ -214,7 +214,6 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], - 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -227,7 +226,6 @@ '--version', '--loglevel', 'login', - 'logout', 'deploy', 'list', 'logs', @@ -282,8 +280,8 @@ '--retries', '--floating', '--tracing', - '--span-exporter-host', - '--span-exporter-port', + '--traces-exporter-host', + '--traces-exporter-port', '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', @@ -342,8 +340,8 @@ '--retries', '--floating', '--tracing', - '--span-exporter-host', - '--span-exporter-port', + '--traces-exporter-host', + '--traces-exporter-port', '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', @@ -371,8 +369,8 @@ '--tls', '--asyncio', '--tracing', - '--span-exporter-host', - '--span-exporter-port', + '--traces-exporter-host', + '--traces-exporter-port', '--metrics', '--metrics-exporter-host', '--metrics-exporter-port', diff --git a/tests/integration/instrumentation/test_flow_instrumentation.py b/tests/integration/instrumentation/test_flow_instrumentation.py index e519d9e612bcf..392791800c2d5 100644 --- a/tests/integration/instrumentation/test_flow_instrumentation.py +++ b/tests/integration/instrumentation/test_flow_instrumentation.py @@ -28,13 +28,13 @@ def test_gateway_instrumentation( f = Flow( protocol=protocol, tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, + traces_exporter_host='localhost', + traces_exporter_port=4317, ).add( uses=ExecutorTestWithTracing, tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, + traces_exporter_host='localhost', + traces_exporter_port=4317, ) with f: @@ -65,8 +65,8 @@ def test_gateway_instrumentation( def test_executor_instrumentation(otlp_collector): f = Flow( tracing=True, - span_exporter_host='localhost', - span_exporter_port=4317, + traces_exporter_host='localhost', + traces_exporter_port=4317, ).add(uses=ExecutorFailureWithTracing) with f: From bb0b0037069da53e259044bea73ef0bab302540a Mon Sep 17 00:00:00 2001 From: Jina Dev Bot Date: Tue, 11 Oct 2022 12:33:35 +0000 Subject: [PATCH 79/79] style: fix overload and cli autocomplete --- jina_cli/autocomplete.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jina_cli/autocomplete.py b/jina_cli/autocomplete.py index 118ec63a6a50a..47f52d815fd55 100644 --- a/jina_cli/autocomplete.py +++ b/jina_cli/autocomplete.py @@ -214,6 +214,7 @@ 'hub status': ['--help', '--id', '--verbose', '--replay'], 'hub': ['--help', 'new', 'push', 'pull', 'status'], 'cloud login': ['--help'], + 'cloud logout': ['--help'], 'cloud deploy': ['--help', '--name', '--workspace', '--env-file'], 'cloud list': ['--help', '--status'], 'cloud logs': ['--help', '--executor'], @@ -226,6 +227,7 @@ '--version', '--loglevel', 'login', + 'logout', 'deploy', 'list', 'logs',