Skip to content

Commit 07ce592

Browse files
authored
update: README gRPC-Python o11y Codelab (#28)
1 parent 838d17d commit 07ce592

File tree

1 file changed

+311
-12
lines changed

1 file changed

+311
-12
lines changed
Lines changed: 311 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,318 @@
1-
# Setup Basic OpenTelemetry Plugin In gRPC-Python
1+
# Setup Basic gRPC OpenTelemetry Plugin for gRPC-Python
22

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
44

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.
59

6-
Designed for developers already familiar with gRPC and wanting to learn how to instrument their gRPC usage with OpenTelemetry.
10+
### **Prerequisites**
711

8-
#### You'll learn how to:
12+
* Python 3.7 or higher
13+
* pip version 9.0.1 or higher
914

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:
1316

14-
## How to use this directory
17+
```console
18+
$ python -m pip install --upgrade pip
19+
```
1520

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

Comments
 (0)