diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ed0f27a1..5a9b67fd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,5 +113,8 @@ significant modifications will be credited to OpenTelemetry Authors. ([#339](https://github.com/open-telemetry/opentelemetry-demo/pull/339)) * Added basic metrics support for recommendation service (Python) ([#416](https://github.com/open-telemetry/opentelemetry-demo/pull/416)) +* Added metrics auto-instrumentation + minor metrics refactor for recommendation + service (Python) + [#432](https://github.com/open-telemetry/opentelemetry-demo/pull/432) * Replaced the Jaeger exporter to the OTLP exporter in the OTel Collector ([#435](https://github.com/open-telemetry/opentelemetry-demo/pull/435)) diff --git a/docker-compose.yml b/docker-compose.yml index da459a06e3..eeacfde224 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -334,8 +334,8 @@ services: - RECOMMENDATION_SERVICE_PORT - PRODUCT_CATALOG_SERVICE_ADDR - OTEL_PYTHON_LOG_CORRELATION=true - - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT - # OTEL_EXPORTER_OTLP_METRICS_ENDPOINT # Not working for Python OTLP exporter + - OTEL_TRACES_EXPORTER=otlp + - OTEL_METRICS_EXPORTER=otlp - OTEL_EXPORTER_OTLP_ENDPOINT - OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE - OTEL_SERVICE_NAME=recommendationservice diff --git a/docs/metric_service_features.md b/docs/metric_service_features.md index 1f410c235d..464ffb6802 100644 --- a/docs/metric_service_features.md +++ b/docs/metric_service_features.md @@ -17,5 +17,5 @@ Emoji Legend | Frontend | JavaScript | :construction: | :construction: | :construction: | :construction: | :construction: | :construction: | | Payment | JavaScript | :construction: | :construction: | :construction: | :construction: | :construction: | :construction: | | Product Catalog | Go | :construction: | :construction: | :construction: | :construction: | :construction: | :construction: | -| Recommendation | Python | :construction: | :100: | :construction: | :construction: | :construction: | :construction: | +| Recommendation | Python | :100: | :100: | :construction: | :construction: | :construction: | :construction: | | Shipping | Rust | :construction: | :construction: | :construction: | :construction: | :construction: | :construction: | diff --git a/docs/services/recommendationservice.md b/docs/services/recommendationservice.md index 6381b41d54..bd61f00f3e 100644 --- a/docs/services/recommendationservice.md +++ b/docs/services/recommendationservice.md @@ -26,10 +26,7 @@ endpoints, resource attributes, and service name are automatically set by the OpenTelemetry auto instrumentor based on environment variables. ```python - tracer_provider = TracerProvider() - trace.set_tracer_provider(tracer_provider) - tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) - tracer = trace.get_tracer("recommendationservice") + tracer = trace.get_tracer_provider().get_tracer("recommendationservice") ``` ### Add attributes to auto-instrumented spans @@ -61,7 +58,33 @@ block ends execution. This is done in the `get_product_list` function. ## Metrics -TBD +### Initialize meter provider + +The OpenTelemetry SDK is initialized in the `__main__` code block. This code +will create a meter provider. Export +endpoints, resource attributes, and service name are automatically set by the +OpenTelemetry auto instrumentor based on environment variables. + +```python + meter = metrics.get_meter_provider().get_meter("recommendationservice") +``` + +### Custom metrics + +The following custom metrics are currently available: + +* `app_recommendations_counter`: Cumulative count of # recommended + products per service call + +### Auto-instrumented metrics + +The following metrics are available through auto-instrumentation, courtesy of +the `opentelemetry-instrumentation-system-metrics`, which is installed as part +of `opentelemetry-bootstrap` on building the recommendationservice Docker image: + +* `runtime.cpython.cpu_time` +* `runtime.cpython.memory` +* `runtime.cpython.gc_count` ## Logs diff --git a/src/recommendationservice/Dockerfile b/src/recommendationservice/Dockerfile index bcc139d87c..a178ae549d 100644 --- a/src/recommendationservice/Dockerfile +++ b/src/recommendationservice/Dockerfile @@ -22,8 +22,10 @@ COPY ./pb/ ./proto/ RUN pip install --upgrade pip RUN pip install -r ./requirements.txt +RUN opentelemetry-bootstrap -a install + # add files into working directory -RUN python -m pip install grpcio-tools +RUN python -m pip install grpcio-tools==1.48.2 RUN python -m grpc_tools.protoc -I=./proto/ --python_out=./ --grpc_python_out=./ ./proto/demo.proto EXPOSE ${RECOMMENDATION_SERVICE_PORT} diff --git a/src/recommendationservice/metrics.py b/src/recommendationservice/metrics.py new file mode 100644 index 0000000000..e9cac3e59e --- /dev/null +++ b/src/recommendationservice/metrics.py @@ -0,0 +1,14 @@ +#!/usr/bin/python + +def init_metrics(meter): + + # Recommendations counter + app_recommendations_counter = meter.create_counter( + 'app_recommendations_counter', unit='recommendations', description="Counts the total number of given recommendations" + ) + + rec_svc_metrics = { + "app_recommendations_counter": app_recommendations_counter, + } + + return rec_svc_metrics diff --git a/src/recommendationservice/recommendation_server.py b/src/recommendationservice/recommendation_server.py index ad17b30a88..87c87a6286 100644 --- a/src/recommendationservice/recommendation_server.py +++ b/src/recommendationservice/recommendation_server.py @@ -17,23 +17,12 @@ # Python import os import random -import time from concurrent import futures # Pip import grpc +from opentelemetry import trace, metrics -# Traces -from opentelemetry import trace -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import (BatchSpanProcessor) -from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter - -# Metrics -from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export import (PeriodicExportingMetricReader) -from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter # Local import demo_pb2 @@ -42,18 +31,23 @@ from grpc_health.v1 import health_pb2_grpc from logger import getJSONLogger +from metrics import ( + init_metrics +) class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer): def ListRecommendations(self, request, context): prod_list = get_product_list(request.product_ids) span = trace.get_current_span() span.set_attribute("app.products_recommended.count", len(prod_list)) - logger.info("[Recv ListRecommendations] product_ids={}".format(prod_list)) - app_recommendations_counter.add(len(prod_list), {'recommendation.type': 'catalog'}) - + logger.info(f"[Recv ListRecommendations] product_ids={prod_list}") # build and return response response = demo_pb2.ListRecommendationsResponse() response.product_ids.extend(prod_list) + + # Collect metrics for this service + rec_svc_metrics["app_recommendations_counter"].add(len(prod_list), {'recommendation.type': 'catalog'}) + return response def Check(self, request, context): @@ -88,6 +82,9 @@ def get_product_list(request_product_ids): indices = random.sample(range(num_products), num_return) # Fetch product ids from indices prod_list = [filtered_products[i] for i in indices] + + span.set_attribute("app.filtered_products.list", prod_list) + return prod_list @@ -98,22 +95,10 @@ def must_map_env(key: str): return value if __name__ == "__main__": - # Initialize tracer provider - tracer_provider = TracerProvider() - trace.set_tracer_provider(tracer_provider) - tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) - tracer = trace.get_tracer("recommendationservice") - - # Initialize meter provider - metric_reader = PeriodicExportingMetricReader(OTLPMetricExporter()) - provider = MeterProvider(metric_readers=[metric_reader]) - metrics.set_meter_provider(provider) - meter = metrics.get_meter(__name__) - - # Create counters - app_recommendations_counter = meter.create_counter( - 'app.recommendations.counter', unit='recommendations', description="Counts the total number of given recommendations" - ) + # Initialize Traces and Metrics + tracer = trace.get_tracer_provider().get_tracer("recommendationservice") + meter = metrics.get_meter_provider().get_meter("recommendationservice") + rec_svc_metrics = init_metrics(meter) port = must_map_env('RECOMMENDATION_SERVICE_PORT') catalog_addr = must_map_env('PRODUCT_CATALOG_SERVICE_ADDR') @@ -131,9 +116,9 @@ def must_map_env(key: str): # Start logger logger = getJSONLogger('recommendationservice-server') - logger.info("RecommendationService listening on port: " + port) + logger.info(f"RecommendationService listening on port: {port}") # Start server - server.add_insecure_port('[::]:' + port) + server.add_insecure_port(f'[::]:{port}') server.start() server.wait_for_termination() diff --git a/src/recommendationservice/requirements.txt b/src/recommendationservice/requirements.txt index 1065829c37..4e948debbf 100644 --- a/src/recommendationservice/requirements.txt +++ b/src/recommendationservice/requirements.txt @@ -1,13 +1,8 @@ google-api-core==2.4.0 grpcio-health-checking==1.43.0 grpcio==1.43.0 -opentelemetry-api==1.13.0 +opentelemetry-distro==0.34b0 opentelemetry-exporter-otlp-proto-grpc==1.13.0 -opentelemetry-instrumentation==0.34b0 -opentelemetry-instrumentation-grpc==0.34b0 -opentelemetry-instrumentation-urllib3==0.34b0 -opentelemetry-sdk==1.13.0 -python-dotenv==0.20.0 -python-json-logger==2.0.2 -requests==2.27.1 -urllib3==1.26.8 \ No newline at end of file +python-dotenv==0.21.0 +python-json-logger==2.0.4 +psutil==5.9.2 # Importing this will also import opentelemetry-instrumentation-system-metrics when running opentelemetry-bootstrap \ No newline at end of file