Skip to content

Commit 40e0e04

Browse files
add: Python otel plugin codelab (#6)
Add Python observability codelab.
1 parent ad357eb commit 40e0e04

12 files changed

+479
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# gRPC Python OpenTelemetry Plugin
2+
3+
Get hands-on with gRPC's OpenTelemetry plugin for Python in this interactive codelab! <!-- TODO(arvindbr8): Insert link once codelab is published. -->
4+
5+
6+
Designed for developers already familiar with gRPC and wanting to learn how to instrument their gRPC usage with OpenTelemetry.
7+
8+
#### You'll learn how to:
9+
10+
- setup gRPC's OpenTelemetry plugin in gRPC Python.
11+
- setup Prometheus exporter with OpenTelemetry.
12+
- explore collected metrics using Prometheus.
13+
14+
## How to use this directory
15+
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.

codelabs/gRPC_Python_OpenTelemetry_Plugin/completed/helloworld_pb2.py

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from google.protobuf import descriptor as _descriptor
2+
from google.protobuf import message as _message
3+
from typing import ClassVar as _ClassVar, Optional as _Optional
4+
5+
DESCRIPTOR: _descriptor.FileDescriptor
6+
7+
class HelloReply(_message.Message):
8+
__slots__ = ["message"]
9+
MESSAGE_FIELD_NUMBER: _ClassVar[int]
10+
message: str
11+
def __init__(self, message: _Optional[str] = ...) -> None: ...
12+
13+
class HelloRequest(_message.Message):
14+
__slots__ = ["name"]
15+
NAME_FIELD_NUMBER: _ClassVar[int]
16+
name: str
17+
def __init__(self, name: _Optional[str] = ...) -> None: ...
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2+
"""Client and server classes corresponding to protobuf-defined services."""
3+
import grpc
4+
5+
import helloworld_pb2 as helloworld__pb2
6+
7+
8+
class GreeterStub(object):
9+
"""The greeting service definition.
10+
"""
11+
12+
def __init__(self, channel):
13+
"""Constructor.
14+
15+
Args:
16+
channel: A grpc.Channel.
17+
"""
18+
self.SayHello = channel.unary_unary(
19+
'/helloworld.Greeter/SayHello',
20+
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
21+
response_deserializer=helloworld__pb2.HelloReply.FromString,
22+
)
23+
24+
25+
class GreeterServicer(object):
26+
"""The greeting service definition.
27+
"""
28+
29+
def SayHello(self, request, context):
30+
"""Sends a greeting
31+
"""
32+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
33+
context.set_details('Method not implemented!')
34+
raise NotImplementedError('Method not implemented!')
35+
36+
37+
def add_GreeterServicer_to_server(servicer, server):
38+
rpc_method_handlers = {
39+
'SayHello': grpc.unary_unary_rpc_method_handler(
40+
servicer.SayHello,
41+
request_deserializer=helloworld__pb2.HelloRequest.FromString,
42+
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
43+
),
44+
}
45+
generic_handler = grpc.method_handlers_generic_handler(
46+
'helloworld.Greeter', rpc_method_handlers)
47+
server.add_generic_rpc_handlers((generic_handler,))
48+
49+
50+
# This class is part of an EXPERIMENTAL API.
51+
class Greeter(object):
52+
"""The greeting service definition.
53+
"""
54+
55+
@staticmethod
56+
def SayHello(request,
57+
target,
58+
options=(),
59+
channel_credentials=None,
60+
call_credentials=None,
61+
insecure=False,
62+
compression=None,
63+
wait_for_ready=None,
64+
timeout=None,
65+
metadata=None):
66+
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHello',
67+
helloworld__pb2.HelloRequest.SerializeToString,
68+
helloworld__pb2.HelloReply.FromString,
69+
options, channel_credentials,
70+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2024 gRPC authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""gRPC Python helloworld.Greeter client for observability codelab."""
15+
16+
import logging
17+
18+
import grpc
19+
import grpc_observability
20+
import helloworld_pb2
21+
import helloworld_pb2_grpc
22+
from opentelemetry.exporter.prometheus import PrometheusMetricReader
23+
from opentelemetry.sdk.metrics import MeterProvider
24+
from prometheus_client import start_http_server
25+
26+
_SERVER_PORT = "50051"
27+
_PROMETHEUS_PORT = 9465
28+
29+
30+
def run():
31+
# Start Prometheus client
32+
start_http_server(port=_PROMETHEUS_PORT, addr="0.0.0.0")
33+
# The default histogram boundaries are not granular enough for RPCs. Consider
34+
# override the "grpc.client.attempt.duration" view similar to csm example.
35+
# Link: https://github.com/grpc/grpc/blob/7407dbf21dbab125ab5eb778daab6182c009b069/examples/python/observability/csm/csm_greeter_client.py#L71C40-L71C53
36+
meter_provider = MeterProvider(metric_readers=[PrometheusMetricReader()])
37+
38+
otel_plugin = grpc_observability.OpenTelemetryPlugin(
39+
meter_provider=meter_provider
40+
)
41+
otel_plugin.register_global()
42+
43+
with grpc.insecure_channel(target=f"localhost:{_SERVER_PORT}") as channel:
44+
stub = helloworld_pb2_grpc.GreeterStub(channel)
45+
# Continuously send RPCs.
46+
while True:
47+
try:
48+
response = stub.SayHello(
49+
helloworld_pb2.HelloRequest(name="You")
50+
)
51+
print(f"Greeter client received: {response.message}")
52+
except grpc.RpcError as rpc_error:
53+
print("Call failed with code: ", rpc_error.code())
54+
55+
# Deregister is not called in this example, but this is required to clean up.
56+
otel_plugin.deregister_global()
57+
58+
59+
if __name__ == "__main__":
60+
logging.basicConfig()
61+
run()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright 2024 gRPC authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""The Python implementation of the GRPC helloworld.Greeter server for observability codelab."""
15+
16+
from concurrent import futures
17+
import logging
18+
19+
import grpc
20+
import grpc_observability
21+
import helloworld_pb2
22+
import helloworld_pb2_grpc
23+
from opentelemetry.exporter.prometheus import PrometheusMetricReader
24+
from opentelemetry.sdk.metrics import MeterProvider
25+
from prometheus_client import start_http_server
26+
27+
_SERVER_PORT = "50051"
28+
_PROMETHEUS_PORT = 9464
29+
30+
31+
class Greeter(helloworld_pb2_grpc.GreeterServicer):
32+
def SayHello(self, request, context):
33+
message = request.name
34+
return helloworld_pb2.HelloReply(message=f"Hello {message}")
35+
36+
37+
def serve():
38+
# Start Prometheus client
39+
start_http_server(port=_PROMETHEUS_PORT, addr="0.0.0.0")
40+
# The default histogram boundaries are not granular enough for RPCs. Consider
41+
# override the "grpc.client.attempt.duration" view similar to csm example.
42+
# Link: https://github.com/grpc/grpc/blob/7407dbf21dbab125ab5eb778daab6182c009b069/examples/python/observability/csm/csm_greeter_client.py#L71C40-L71C53
43+
meter_provider = MeterProvider(metric_readers=[PrometheusMetricReader()])
44+
45+
otel_plugin = grpc_observability.OpenTelemetryPlugin(
46+
meter_provider=meter_provider
47+
)
48+
otel_plugin.register_global()
49+
50+
server = grpc.server(
51+
thread_pool=futures.ThreadPoolExecutor(max_workers=10),
52+
)
53+
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
54+
server.add_insecure_port("[::]:" + _SERVER_PORT)
55+
server.start()
56+
print("Server started, listening on " + _SERVER_PORT)
57+
58+
server.wait_for_termination()
59+
60+
# Deregister is not called in this example, but this is required to clean up.
61+
otel_plugin.deregister_global()
62+
63+
64+
if __name__ == "__main__":
65+
logging.basicConfig()
66+
serve()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
grpcio>=1.65.5
2+
protobuf>=5.26.1,<6.0dev
3+
grpcio-observability>=1.65.5
4+
opentelemetry-sdk==1.26.0
5+
opentelemetry-api==1.26.0
6+
opentelemetry-exporter-prometheus==0.47b0

codelabs/gRPC_Python_OpenTelemetry_Plugin/start_here/helloworld_pb2.py

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from google.protobuf import descriptor as _descriptor
2+
from google.protobuf import message as _message
3+
from typing import ClassVar as _ClassVar, Optional as _Optional
4+
5+
DESCRIPTOR: _descriptor.FileDescriptor
6+
7+
class HelloReply(_message.Message):
8+
__slots__ = ["message"]
9+
MESSAGE_FIELD_NUMBER: _ClassVar[int]
10+
message: str
11+
def __init__(self, message: _Optional[str] = ...) -> None: ...
12+
13+
class HelloRequest(_message.Message):
14+
__slots__ = ["name"]
15+
NAME_FIELD_NUMBER: _ClassVar[int]
16+
name: str
17+
def __init__(self, name: _Optional[str] = ...) -> None: ...
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2+
"""Client and server classes corresponding to protobuf-defined services."""
3+
import grpc
4+
5+
import helloworld_pb2 as helloworld__pb2
6+
7+
8+
class GreeterStub(object):
9+
"""The greeting service definition.
10+
"""
11+
12+
def __init__(self, channel):
13+
"""Constructor.
14+
15+
Args:
16+
channel: A grpc.Channel.
17+
"""
18+
self.SayHello = channel.unary_unary(
19+
'/helloworld.Greeter/SayHello',
20+
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
21+
response_deserializer=helloworld__pb2.HelloReply.FromString,
22+
)
23+
24+
25+
class GreeterServicer(object):
26+
"""The greeting service definition.
27+
"""
28+
29+
def SayHello(self, request, context):
30+
"""Sends a greeting
31+
"""
32+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
33+
context.set_details('Method not implemented!')
34+
raise NotImplementedError('Method not implemented!')
35+
36+
37+
def add_GreeterServicer_to_server(servicer, server):
38+
rpc_method_handlers = {
39+
'SayHello': grpc.unary_unary_rpc_method_handler(
40+
servicer.SayHello,
41+
request_deserializer=helloworld__pb2.HelloRequest.FromString,
42+
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
43+
),
44+
}
45+
generic_handler = grpc.method_handlers_generic_handler(
46+
'helloworld.Greeter', rpc_method_handlers)
47+
server.add_generic_rpc_handlers((generic_handler,))
48+
49+
50+
# This class is part of an EXPERIMENTAL API.
51+
class Greeter(object):
52+
"""The greeting service definition.
53+
"""
54+
55+
@staticmethod
56+
def SayHello(request,
57+
target,
58+
options=(),
59+
channel_credentials=None,
60+
call_credentials=None,
61+
insecure=False,
62+
compression=None,
63+
wait_for_ready=None,
64+
timeout=None,
65+
metadata=None):
66+
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHello',
67+
helloworld__pb2.HelloRequest.SerializeToString,
68+
helloworld__pb2.HelloReply.FromString,
69+
options, channel_credentials,
70+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

0 commit comments

Comments
 (0)