|
1 |
| -# Setup Basic OpenTelemetry Plugin In gRPC-Python |
| 1 | +# Setup Basic gRPC OpenTelemetry Plugin for gRPC-Python |
2 | 2 |
|
3 |
| -Get hands-on with gRPC's OpenTelemetry plugin for Python in this interactive codelab! <!-- TODO(arvindbr8): Insert link once codelab is published. --> |
| 3 | +## Before you Begin |
4 | 4 |
|
| 5 | +Get hands-on with gRPC's OpenTelemetry plugin for gRPC-Python in this |
| 6 | +interactive codelab! <!-- TODO(arvindbr8): Insert link once codelab is published. --> |
| 7 | +Designed for developers already familiar with gRPC and wanting to learn how to |
| 8 | +instrument their gRPC usage with OpenTelemetry. |
5 | 9 |
|
6 |
| -Designed for developers already familiar with gRPC and wanting to learn how to instrument their gRPC usage with OpenTelemetry. |
| 10 | +### **Prerequisites** |
7 | 11 |
|
8 |
| -#### You'll learn how to: |
| 12 | +* Python 3.7 or higher |
| 13 | +* pip version 9.0.1 or higher |
9 | 14 |
|
10 |
| -- setup gRPC's OpenTelemetry plugin in gRPC Python. |
11 |
| -- setup Prometheus exporter with OpenTelemetry. |
12 |
| -- explore collected metrics using Prometheus. |
| 15 | +If necessary, upgrade your version of pip: |
13 | 16 |
|
14 |
| -## How to use this directory |
| 17 | +```console |
| 18 | +$ python -m pip install --upgrade pip |
| 19 | +``` |
15 | 20 |
|
16 |
| -- [start_here](start_here/) directory serves as a starting point for the |
17 |
| -codelab. |
18 |
| -- [completed](completed/) directory showcases the finished code, giving you a |
19 |
| -peek of how the final implementation should look like. |
| 21 | +If you cannot upgrade pip due to a system-owned installation, you can run the |
| 22 | +example in a virtualenv: |
| 23 | + |
| 24 | +```console |
| 25 | +$ python -m pip install virtualenv |
| 26 | +$ virtualenv venv |
| 27 | +$ source venv/bin/activate |
| 28 | +$ python -m pip install --upgrade pip |
| 29 | +``` |
| 30 | + |
| 31 | +* Use [this as a starting point](start_here/) for this codelab. |
| 32 | + |
| 33 | +#### Install dependencies |
| 34 | + |
| 35 | +```console |
| 36 | +$ cd codelabs/gRPC_Python_OpenTelemetry_Plugin/ |
| 37 | +$ python -m pip install -r requirements.txt |
| 38 | +``` |
| 39 | + |
| 40 | +### **What you’ll learn** |
| 41 | + |
| 42 | +* How to setup OpenTelemetry Plugin for existing gRPC Python application |
| 43 | +* Running a local Prometeus instance |
| 44 | +* Exporting metrics to Prometeus |
| 45 | +* View metrics from Prometeus dashboard |
| 46 | + |
| 47 | +### **What you’ll need** |
| 48 | + |
| 49 | +* A computer with internet connection |
| 50 | + |
| 51 | +## Instrumenting applications with gRPC OpenTelemetry Plugin |
| 52 | + |
| 53 | +The client and server uses a simple gRPC HelloWorld example that we will instrument with the gRPC OpenTelemetry plugin. |
| 54 | + |
| 55 | +### **Setup instrumentation on the client** |
| 56 | + |
| 57 | +Open `codelabs/grpc-python-opentelemetry/start_here/observability_greeter_client.py` with your favorite editor, first add related dependencies and macros: |
| 58 | + |
| 59 | +```python |
| 60 | +import logging |
| 61 | +import time |
| 62 | + |
| 63 | +import grpc |
| 64 | +import grpc_observability |
| 65 | +import helloworld_pb2 |
| 66 | +import helloworld_pb2_grpc |
| 67 | +from opentelemetry.exporter.prometheus import PrometheusMetricReader |
| 68 | +from opentelemetry.sdk.metrics import MeterProvider |
| 69 | +from prometheus_client import start_http_server |
| 70 | +``` |
| 71 | + |
| 72 | +Then transform `run()` to look like |
| 73 | + |
| 74 | +```python |
| 75 | +def run(): |
| 76 | + # Start Prometheus client |
| 77 | + start_http_server(port=_PROMETHEUS_PORT, addr="0.0.0.0") |
| 78 | + # The default histogram boundaries are not granular enough for RPCs. Consider |
| 79 | + # override the "grpc.client.attempt.duration" view similar to csm example. |
| 80 | + # Link: https://github.com/grpc/grpc/blob/7407dbf21dbab125ab5eb778daab6182c009b069/examples/python/observability/csm/csm_greeter_client.py#L71C40-L71C53 |
| 81 | + meter_provider = MeterProvider(metric_readers=[PrometheusMetricReader()]) |
| 82 | + |
| 83 | + otel_plugin = grpc_observability.OpenTelemetryPlugin( |
| 84 | + meter_provider=meter_provider |
| 85 | + ) |
| 86 | + otel_plugin.register_global() |
| 87 | + |
| 88 | + with grpc.insecure_channel(target=f"localhost:{_SERVER_PORT}") as channel: |
| 89 | + stub = helloworld_pb2_grpc.GreeterStub(channel) |
| 90 | + # Continuously send RPCs every second. |
| 91 | + while True: |
| 92 | + try: |
| 93 | + response = stub.SayHello(helloworld_pb2.HelloRequest(name="You")) |
| 94 | + print(f"Greeter client received: {response.message}") |
| 95 | + time.sleep(1) |
| 96 | + except grpc.RpcError as rpc_error: |
| 97 | + print("Call failed with code: ", rpc_error.code()) |
| 98 | + |
| 99 | + # Deregister is not called in this example, but this is required to clean up. |
| 100 | + otel_plugin.deregister_global() |
| 101 | +``` |
| 102 | + |
| 103 | +> [!NOTE] |
| 104 | +> How a Prometheus Exporter is being set up on the OpenTelemetry Meter Provider. (There are other ways to export the metrics as well. This codelab chooses the prometheus exporter.) |
| 105 | +
|
| 106 | +This MeterProvider is provided to gRPC’s OpenTelemetry plugin as configuration. |
| 107 | +Once the OpenTelemetry plugin is registered globally all gRPC clients and |
| 108 | +servers will be instrumented with OpenTelemetry. |
| 109 | + |
| 110 | +### **Setup instrumentation on the server** |
| 111 | + |
| 112 | +Similarly, let’s add the OpenTelemetry plugin to the server as well. Open `codelabs/grpc-python-opentelemetry/start_here/observability_greeter_server.py` and change dependencies and macros to this |
| 113 | + |
| 114 | +```python |
| 115 | +from concurrent import futures |
| 116 | +import logging |
| 117 | + |
| 118 | +import grpc |
| 119 | +import grpc_observability |
| 120 | +import helloworld_pb2 |
| 121 | +import helloworld_pb2_grpc |
| 122 | +from opentelemetry.sdk.metrics import MeterProvider |
| 123 | +from opentelemetry.exporter.prometheus import PrometheusMetricReader |
| 124 | +from prometheus_client import start_http_server |
| 125 | + |
| 126 | +_SERVER_PORT = "50051" |
| 127 | +_PROMETHEUS_PORT = 9464 |
| 128 | +``` |
| 129 | + |
| 130 | +Then transform `serve()` to look like this |
| 131 | + |
| 132 | +```python |
| 133 | +def serve(): |
| 134 | + # Start Prometheus client |
| 135 | + start_http_server(port=_PROMETHEUS_PORT, addr="0.0.0.0") |
| 136 | + # The default histogram boundaries are not granular enough for RPCs. Consider |
| 137 | + # override the "grpc.client.attempt.duration" view similar to csm example. |
| 138 | + # Link: https://github.com/grpc/grpc/blob/7407dbf21dbab125ab5eb778daab6182c009b069/examples/python/observability/csm/csm_greeter_client.py#L71C40-L71C53 |
| 139 | + meter_provider = MeterProvider(metric_readers=[PrometheusMetricReader()]) |
| 140 | + |
| 141 | + otel_plugin = grpc_observability.OpenTelemetryPlugin( |
| 142 | + meter_provider=meter_provider |
| 143 | + ) |
| 144 | + otel_plugin.register_global() |
| 145 | + |
| 146 | + server = grpc.server( |
| 147 | + thread_pool=futures.ThreadPoolExecutor(max_workers=10), |
| 148 | + ) |
| 149 | + helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server) |
| 150 | + server.add_insecure_port("[::]:" + _SERVER_PORT) |
| 151 | + server.start() |
| 152 | + print("Server started, listening on " + _SERVER_PORT) |
| 153 | + |
| 154 | + server.wait_for_termination() |
| 155 | + |
| 156 | + # Deregister is not called in this example, but this is required to clean up. |
| 157 | + otel_plugin.deregister_global() |
| 158 | +``` |
| 159 | + |
| 160 | +## Running the example and viewing metrics |
| 161 | + |
| 162 | +To run the server, run |
| 163 | + |
| 164 | +```console |
| 165 | +$ cd server |
| 166 | +$ python -m observability_greeter_server |
| 167 | +``` |
| 168 | + |
| 169 | +With a successful setup, you will see the following output for the server \- |
| 170 | + |
| 171 | +```console |
| 172 | +Server started, listening on 50051 |
| 173 | +``` |
| 174 | + |
| 175 | +While, the server is running, on another terminal, run \- |
| 176 | + |
| 177 | +```console |
| 178 | +$ cd client |
| 179 | +$ python -m observability_greeter_client |
| 180 | +``` |
| 181 | + |
| 182 | +A successful run will look like \- |
| 183 | + |
| 184 | +```console |
| 185 | +Greeter client received: Hello You |
| 186 | +Greeter client received: Hello You |
| 187 | +Greeter client received: Hello You |
| 188 | +``` |
| 189 | + |
| 190 | +Since we have set-up the gRPC OpenTelemetry plugin to export metrics using Prometheus. Those metrics will be available on localhost:9464 for server and localhost:9465 for client. |
| 191 | + |
| 192 | +To see client metrics \- |
| 193 | + |
| 194 | +```console |
| 195 | +$ curl localhost:9465/metrics |
| 196 | +``` |
| 197 | + |
| 198 | +The result would be of the form \- |
| 199 | + |
| 200 | +```console |
| 201 | +# HELP python_gc_objects_collected_total Objects collected during gc |
| 202 | +# TYPE python_gc_objects_collected_total counter |
| 203 | +python_gc_objects_collected_total{generation="0"} 241.0 |
| 204 | +python_gc_objects_collected_total{generation="1"} 163.0 |
| 205 | +python_gc_objects_collected_total{generation="2"} 0.0 |
| 206 | +# HELP python_gc_objects_uncollectable_total Uncollectable objects found during GC |
| 207 | +# TYPE python_gc_objects_uncollectable_total counter |
| 208 | +python_gc_objects_uncollectable_total{generation="0"} 0.0 |
| 209 | +python_gc_objects_uncollectable_total{generation="1"} 0.0 |
| 210 | +python_gc_objects_uncollectable_total{generation="2"} 0.0 |
| 211 | +# HELP python_gc_collections_total Number of times this generation was collected |
| 212 | +# TYPE python_gc_collections_total counter |
| 213 | +python_gc_collections_total{generation="0"} 78.0 |
| 214 | +python_gc_collections_total{generation="1"} 7.0 |
| 215 | +python_gc_collections_total{generation="2"} 0.0 |
| 216 | +# HELP python_info Python platform information |
| 217 | +# TYPE python_info gauge |
| 218 | +python_info{implementation="CPython",major="3",minor="10",patchlevel="9",version="3.10.9"} 1.0 |
| 219 | +# HELP process_virtual_memory_bytes Virtual memory size in bytes. |
| 220 | +# TYPE process_virtual_memory_bytes gauge |
| 221 | +process_virtual_memory_bytes 1.868988416e+09 |
| 222 | +# HELP process_resident_memory_bytes Resident memory size in bytes. |
| 223 | +# TYPE process_resident_memory_bytes gauge |
| 224 | +process_resident_memory_bytes 4.1680896e+07 |
| 225 | +# TYPE process_resident_memory_bytes gauge 21:20:16 [154/966] |
| 226 | +process_resident_memory_bytes 4.1680896e+07 |
| 227 | +# HELP process_start_time_seconds Start time of the process since unix epoch in seconds. |
| 228 | +# TYPE process_start_time_seconds gauge |
| 229 | +process_start_time_seconds 1.72375679833e+09 |
| 230 | +# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. |
| 231 | +# TYPE process_cpu_seconds_total counter |
| 232 | +process_cpu_seconds_total 0.38 |
| 233 | +# HELP process_open_fds Number of open file descriptors. |
| 234 | +# TYPE process_open_fds gauge |
| 235 | +process_open_fds 9.0 |
| 236 | +# HELP process_max_fds Maximum number of open file descriptors. |
| 237 | +# TYPE process_max_fds gauge |
| 238 | +process_max_fds 4096.0 |
| 239 | +# HELP target_info Target metadata |
| 240 | +# TYPE target_info gauge |
| 241 | +target_info{service_name="unknown_service",telemetry_sdk_language="python",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="1.26.0"} 1.0 |
| 242 | +# HELP grpc_client_attempt_started_total Number of client call attempts started |
| 243 | +# TYPE grpc_client_attempt_started_total counter |
| 244 | +grpc_client_attempt_started_total{grpc_method="other",grpc_target="localhost:50051"} 18.0 |
| 245 | +# HELP grpc_client_attempt_sent_total_compressed_message_size_bytes Compressed message bytes sent per client call attempt |
| 246 | +# TYPE grpc_client_attempt_sent_total_compressed_message_size_bytes histogram |
| 247 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="0.0"} 0.0 |
| 248 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="5.0"} 18.0 |
| 249 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="10.0"} 18.0 |
| 250 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="25.0"} 18.0 |
| 251 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="50.0"} 18.0 |
| 252 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="75.0"} 18.0 |
| 253 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="100.0"} 18.0 |
| 254 | +grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="other",grpc_status="OK",grpc_target="localhost:50051",le="250.0"} 18.0 |
| 255 | +``` |
| 256 | + |
| 257 | +Similarly, for the server side \- |
| 258 | + |
| 259 | +```console |
| 260 | +$ curl localhost:9464/metrics |
| 261 | +``` |
| 262 | + |
| 263 | +## Viewing metrics on Prometheus |
| 264 | + |
| 265 | +Here, we will setup a prometheus instance that will scrape our gRPC example client and server that are exporting metrics using prometheus. |
| 266 | + |
| 267 | +[Download the latest release](https://prometheus.io/download) of Prometheus for your platform, then extract and run it: |
| 268 | + |
| 269 | +```console |
| 270 | +$ tar xvfz prometheus-*.tar.gz |
| 271 | +$ cd prometheus-* |
| 272 | +``` |
| 273 | + |
| 274 | +Create a prometheus configuration file with the following \- |
| 275 | + |
| 276 | +```console |
| 277 | +$ cat > grpc_otel_python_prometheus.yml <<EOF |
| 278 | +scrape_configs: |
| 279 | + - job_name: "prometheus" |
| 280 | + scrape_interval: 5s |
| 281 | + static_configs: |
| 282 | + - targets: ["localhost:9090"] |
| 283 | + - job_name: "grpc-otel-python" |
| 284 | + scrape_interval: 5s |
| 285 | + static_configs: |
| 286 | + - targets: ["localhost:9464", "localhost:9465"] |
| 287 | +EOF |
| 288 | +``` |
| 289 | + |
| 290 | +Start prometheus with the new configuration \- |
| 291 | + |
| 292 | +```console |
| 293 | +$ ./prometheus --config.file=grpc_otel_python_prometheus.yml |
| 294 | +``` |
| 295 | + |
| 296 | +This will configure the metrics from the client and server codelab processes to be scraped every 5 seconds. |
| 297 | + |
| 298 | +Go to [http://localhost:9090/graph](http://localhost:9090/graph) to view the metrics. For example, the query \- |
| 299 | + |
| 300 | +``` |
| 301 | +histogram_quantile(0.5, rate(grpc_client_attempt_duration_seconds_bucket[1m])) |
| 302 | +``` |
| 303 | + |
| 304 | +will show a graph with the median attempt latency using 1minute as the time window for the quantile calculation. |
| 305 | + |
| 306 | +Rate of queries \- |
| 307 | + |
| 308 | +``` |
| 309 | +increase(grpc_client_attempt_duration_seconds_bucket[1m]) |
| 310 | +``` |
| 311 | + |
| 312 | +## (Optional) Exercise for User |
| 313 | + |
| 314 | +In the prometheus dashboards, you’ll notice that the QPS is low. See if you spot some suspicious code in the example that is limiting the QPS. |
| 315 | + |
| 316 | +The client also sleeps for 1 second between RPCs. This can be removed as well. |
| 317 | + |
| 318 | +For the enthusiastic, the client code limits itself to only have a single pending RPC at a given moment. This can be modified so that the client sends more RPCs without waiting for the previous ones to complete. (The solution for this has not been provided.) |
0 commit comments