Skip to content

Commit d8a68b7

Browse files
authored
version 0.1.0
* add setup.py * version 0.1.0
1 parent 9e2f1b1 commit d8a68b7

File tree

5 files changed

+212
-0
lines changed

5 files changed

+212
-0
lines changed

python_grpc_prometheus/__init__.py

Whitespace-only changes.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import grpc
2+
import time
3+
4+
from timeit import default_timer
5+
6+
from .server_metrics import SERVER_HANDLED_LATENCY_SECONDS
7+
from .server_metrics import SERVER_HANDLED_COUNTER
8+
from .server_metrics import SERVER_STARTED_COUNTER
9+
10+
from .util import type_from_method
11+
from .util import code_to_string
12+
13+
14+
def _wrap_rpc_behavior(handler, fn):
15+
if handler is None:
16+
return None
17+
18+
if handler.request_streaming and handler.response_streaming:
19+
behavior_fn = handler.stream_stream
20+
handler_factory = grpc.stream_stream_rpc_method_handler
21+
elif handler.request_streaming and not handler.response_streaming:
22+
behavior_fn = handler.stream_unary
23+
handler_factory = grpc.stream_unary_rpc_method_handler
24+
elif not handler.request_streaming and handler.response_streaming:
25+
behavior_fn = handler.unary_stream
26+
handler_factory = grpc.unary_stream_rpc_method_handler
27+
else:
28+
behavior_fn = handler.unary_unary
29+
handler_factory = grpc.unary_unary_rpc_method_handler
30+
31+
return handler_factory(fn(behavior_fn,
32+
handler.request_streaming,
33+
handler.response_streaming),
34+
request_deserializer=handler.request_deserializer,
35+
response_serializer=handler.response_serializer)
36+
37+
38+
class PromServerInterceptor(grpc.ServerInterceptor):
39+
40+
def intercept_service(self, continuation, handler_call_details):
41+
42+
handler = continuation(handler_call_details)
43+
44+
# only support unary
45+
if handler.request_streaming or handler.response_streaming:
46+
return handler
47+
48+
client_call_method = handler_call_details.method
49+
ss = client_call_method.split("/")
50+
if len(ss) < 3:
51+
return continuation(handler_call_details)
52+
grpc_service = ss[1]
53+
grpc_method = ss[2]
54+
grpc_type = type_from_method(handler.request_streaming, handler.response_streaming)
55+
56+
SERVER_STARTED_COUNTER.labels(
57+
grpc_type=grpc_type,
58+
grpc_service=grpc_service,
59+
grpc_method=grpc_method).inc()
60+
61+
def latency_wrapper(behavior, request_streaming, response_streaming):
62+
def new_behavior(request_or_iterator, service_context):
63+
start = default_timer()
64+
try:
65+
rsp = behavior(request_or_iterator, service_context)
66+
SERVER_HANDLED_COUNTER.labels(
67+
grpc_type=grpc_type,
68+
grpc_service=grpc_service,
69+
grpc_method=grpc_method,
70+
grpc_code=code_to_string(service_context._state.code)
71+
).inc()
72+
return rsp
73+
except grpc.RpcError as e:
74+
if isinstance(e, grpc.Call):
75+
SERVER_HANDLED_COUNTER.labels(
76+
grpc_type=grpc_type,
77+
grpc_service=grpc_service,
78+
grpc_method=grpc_method,
79+
grpc_code=code_to_string(e.code())
80+
).inc()
81+
raise e
82+
finally:
83+
SERVER_HANDLED_LATENCY_SECONDS.labels(
84+
grpc_type=grpc_type,
85+
grpc_service=grpc_service,
86+
grpc_method=grpc_method).observe(max(default_timer() - start, 0))
87+
88+
return new_behavior
89+
90+
return _wrap_rpc_behavior(continuation(handler_call_details), latency_wrapper)
91+
92+
93+
class ServiceLatencyInterceptor(grpc.ServerInterceptor):
94+
95+
def __init__(self):
96+
pass
97+
98+
def intercept_service(self, continuation, handler_call_details):
99+
client_call_method = handler_call_details.method
100+
parts = client_call_method.split("/")
101+
grpc_service = parts[1]
102+
grpc_method = parts[2]
103+
104+
def latency_wrapper(behavior, request_streaming, response_streaming):
105+
def new_behavior(request_or_iterator, service_context):
106+
start = time.time()
107+
try:
108+
return behavior(request_or_iterator, service_context)
109+
finally:
110+
SERVER_HANDLED_LATENCY_SECONDS.labels(
111+
grpc_type='UNARY',
112+
grpc_service=grpc_service,
113+
grpc_method=grpc_method).observe(max(time.time() - start, 0))
114+
115+
return new_behavior
116+
117+
return _wrap_rpc_behavior(continuation(handler_call_details), latency_wrapper)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from prometheus_client import Counter
2+
from prometheus_client import Histogram
3+
4+
SERVER_STARTED_COUNTER = Counter(
5+
'grpc_server_started_total',
6+
'Total number of RPCs started on the server.',
7+
["grpc_type", "grpc_service", "grpc_method"])
8+
9+
SERVER_HANDLED_COUNTER = Counter(
10+
'grpc_server_handled_total',
11+
'Total number of RPCs completed on the server, regardless of success or failure.',
12+
["grpc_type", "grpc_service", "grpc_method", "grpc_code"])
13+
14+
SERVER_HANDLED_LATENCY_SECONDS = Histogram(
15+
'grpc_server_handling_seconds',
16+
'Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.',
17+
["grpc_type", "grpc_service", "grpc_method"])
18+
19+
SERVER_MSG_RECEIVED_TOTAL = Counter(
20+
'grpc_server_msg_received_total',
21+
'Total number of RPC stream messages received on the server.',
22+
["grpc_type", "grpc_service", "grpc_method"])
23+
24+
SERVER_MSG_SENT_TOTAL = Counter(
25+
'grpc_server_msg_sent_total',
26+
'Total number of gRPC stream messages sent by the server.',
27+
["grpc_type", "grpc_service", "grpc_method"])

python_grpc_prometheus/util.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import grpc
2+
3+
Unary = "unary"
4+
ClientStream = "client_stream"
5+
ServerStream = "server_stream"
6+
BidiStream = "bidi_stream"
7+
8+
9+
def type_from_method(request_streaming, response_streaming):
10+
if not request_streaming and not response_streaming:
11+
return Unary
12+
elif request_streaming and not response_streaming:
13+
return ClientStream
14+
elif not request_streaming and response_streaming:
15+
return ServerStream
16+
else:
17+
return BidiStream
18+
19+
20+
GRPC_STATUS_CODE_TO_STRING = {
21+
grpc.StatusCode.OK: "OK",
22+
grpc.StatusCode.CANCELLED: "Canceled",
23+
grpc.StatusCode.UNKNOWN: "Unknown",
24+
grpc.StatusCode.INVALID_ARGUMENT: "InvalidArgument",
25+
grpc.StatusCode.DEADLINE_EXCEEDED: "DeadlineExceeded",
26+
grpc.StatusCode.NOT_FOUND: "NotFound",
27+
grpc.StatusCode.ALREADY_EXISTS: "AlreadyExists",
28+
grpc.StatusCode.PERMISSION_DENIED: "PermissionDenied",
29+
grpc.StatusCode.UNAUTHENTICATED: "Unauthenticated",
30+
grpc.StatusCode.RESOURCE_EXHAUSTED: "ResourceExhausted",
31+
grpc.StatusCode.FAILED_PRECONDITION: "FailedPrecondition",
32+
grpc.StatusCode.ABORTED: "Aborted",
33+
grpc.StatusCode.OUT_OF_RANGE: "OutOfRange",
34+
grpc.StatusCode.UNIMPLEMENTED: "Unimplemented",
35+
grpc.StatusCode.INTERNAL: "Internal",
36+
grpc.StatusCode.UNAVAILABLE: "Unavailable",
37+
grpc.StatusCode.DATA_LOSS: "DataLoss",
38+
}
39+
40+
41+
def code_to_string(code):
42+
s = GRPC_STATUS_CODE_TO_STRING.get(code)
43+
if s:
44+
return s
45+
else:
46+
return code.name

setup.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
from setuptools import find_packages
3+
from setuptools import setup
4+
5+
with open("README.md", "r") as fh:
6+
long_description = fh.read()
7+
8+
setup(name='python_grpc_prometheus',
9+
version='0.1.0',
10+
description='Python gRPC Prometheus Interceptors',
11+
long_description=long_description,
12+
long_description_content_type="text/markdown",
13+
author='Zhang Yong',
14+
author_email='[email protected]',
15+
url="https://github.com/zhyon404/python-grpc-prometheus",
16+
install_requires=[
17+
'setuptools==39.0.1',
18+
'prometheus_client==0.3.1'
19+
],
20+
licence='MIT',
21+
packages=find_packages(exclude=["tests.*", "tests"]),
22+
)

0 commit comments

Comments
 (0)