Skip to content

Commit

Permalink
[Python O11Y] Add observability example
Browse files Browse the repository at this point in the history
  • Loading branch information
XuanWang-Amos committed Jan 23, 2024
1 parent 1a20f21 commit c84eea0
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 0 deletions.
59 changes: 59 additions & 0 deletions examples/python/observability/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
gRPC Observability Example
=====================

The examples here demonstrate how to setup gRPC Python Observability with Opentelemetry.

More details about how to use gRPC Python Observability APIs can be found in [OpenTelemetry Metrics gRFC](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md#opentelemetry-metrics).

### Requirements

The examples here depends on grpcio and grpcio-observability version of 1.62.0 or newer.

### Run the Server

1. Navigate to this directory:

```sh
cd grpc/examples/python/observability
```

2. Run the server:

```sh
python -m observability_greeter_server
```

### Run the Client

Note that client should start within 10 seconds of the server becoming active.

```sh
python -m observability_greeter_client
```

### Verifying Metrics

The example will print a list of metric names collected.

Server Side:

```sh
Server started, listening on 50051
Metrics exported on Server side:
grpc.server.call.started
grpc.server.call.sent_total_compressed_message_size
grpc.server.call.rcvd_total_compressed_message_size
grpc.server.call.duration
```

Client Side:

```sh
Greeter client received: Hello You
Metrics exported on client side:
grpc.client.call.duration
grpc.client.attempt.started
grpc.client.attempt.sent_total_compressed_message_size
grpc.client.attempt.rcvd_total_compressed_message_size
grpc.client.attempt.duration
```
30 changes: 30 additions & 0 deletions examples/python/observability/helloworld_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions examples/python/observability/helloworld_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Optional as _Optional

DESCRIPTOR: _descriptor.FileDescriptor

class HelloReply(_message.Message):
__slots__ = ["message"]
MESSAGE_FIELD_NUMBER: _ClassVar[int]
message: str
def __init__(self, message: _Optional[str] = ...) -> None: ...

class HelloRequest(_message.Message):
__slots__ = ["name"]
NAME_FIELD_NUMBER: _ClassVar[int]
name: str
def __init__(self, name: _Optional[str] = ...) -> None: ...
70 changes: 70 additions & 0 deletions examples/python/observability/helloworld_pb2_grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

import helloworld_pb2 as helloworld__pb2


class GreeterStub(object):
"""The greeting service definition.
"""

def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.SayHello = channel.unary_unary(
'/helloworld.Greeter/SayHello',
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
response_deserializer=helloworld__pb2.HelloReply.FromString,
)


class GreeterServicer(object):
"""The greeting service definition.
"""

def SayHello(self, request, context):
"""Sends a greeting
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_GreeterServicer_to_server(servicer, server):
rpc_method_handlers = {
'SayHello': grpc.unary_unary_rpc_method_handler(
servicer.SayHello,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'helloworld.Greeter', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))


# This class is part of an EXPERIMENTAL API.
class Greeter(object):
"""The greeting service definition.
"""

@staticmethod
def SayHello(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHello',
helloworld__pb2.HelloRequest.SerializeToString,
helloworld__pb2.HelloReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
71 changes: 71 additions & 0 deletions examples/python/observability/observability_greeter_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2024 gRPC 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.
"""gRPC Python helloworld.Greeter client with observability enabled."""

from collections import defaultdict
import logging
import time
from typing import Optional

import grpc
import grpc_observability
import helloworld_pb2
import helloworld_pb2_grpc
import open_telemetry_exporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader

OTEL_EXPORT_INTERVAL_S = 0.5


class BaseOpenTelemetryPlugin(grpc_observability.OpenTelemetryPlugin):
def __init__(self, provider: MeterProvider):
self.provider = provider

def get_meter_provider(self) -> Optional[MeterProvider]:
return self.provider


def run():
all_metrics = defaultdict(list)
otel_exporter = open_telemetry_exporter.OTelMetricExporter(all_metrics)
reader = PeriodicExportingMetricReader(
exporter=otel_exporter,
export_interval_millis=OTEL_EXPORT_INTERVAL_S * 1000,
)
provider = MeterProvider(metric_readers=[reader])
otel_plugin = BaseOpenTelemetryPlugin(provider)

with grpc_observability.OpenTelemetryObservability(plugins=[otel_plugin]):
with grpc.insecure_channel(target="localhost:50051") as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
try:
response = stub.SayHello(
helloworld_pb2.HelloRequest(name="You")
)
print(f"Greeter client received: {response.message}")
except grpc.RpcError as rpc_error:
print("Call failed with code: ", rpc_error.code())

# Sleep to make sure all metrics are exported.
time.sleep(5)

print("Metrics exported on client side:")
for metric in all_metrics:
print(metric)


if __name__ == "__main__":
logging.basicConfig()
run()
80 changes: 80 additions & 0 deletions examples/python/observability/observability_greeter_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright 2024 gRPC 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.
"""The Python implementation of the GRPC helloworld.Greeter server with observability enabled."""

from collections import defaultdict
from concurrent import futures
import logging
import time
from typing import Optional

import grpc
import grpc_observability
import helloworld_pb2
import helloworld_pb2_grpc
import open_telemetry_exporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader

OTEL_EXPORT_INTERVAL_S = 0.5


class BaseOpenTelemetryPlugin(grpc_observability.OpenTelemetryPlugin):
def __init__(self, provider: MeterProvider):
self.provider = provider

def get_meter_provider(self) -> Optional[MeterProvider]:
return self.provider


class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
message = request.name
return helloworld_pb2.HelloReply(message=f"Hello {message}")


def serve():
all_metrics = defaultdict(list)
otel_exporter = open_telemetry_exporter.OTelMetricExporter(
all_metrics, print_live=False
)
reader = PeriodicExportingMetricReader(
exporter=otel_exporter,
export_interval_millis=OTEL_EXPORT_INTERVAL_S * 1000,
)
provider = MeterProvider(metric_readers=[reader])
otel_plugin = BaseOpenTelemetryPlugin(provider)
port = "50051"

with grpc_observability.OpenTelemetryObservability(plugins=[otel_plugin]):
server = grpc.server(
thread_pool=futures.ThreadPoolExecutor(max_workers=10),
)
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port("[::]:" + port)
server.start()
print("Server started, listening on " + port)

# Sleep to make sure client made RPC call and all metrics are exported.
time.sleep(10)
print("Metrics exported on Server side:")
for metric in all_metrics:
print(metric)

server.stop(0)


if __name__ == "__main__":
logging.basicConfig()
serve()
Loading

0 comments on commit c84eea0

Please sign in to comment.