-
Notifications
You must be signed in to change notification settings - Fork 611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python Monitoring instrumentation - Proof of Concept #269
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,18 +24,44 @@ | |
import sys | ||
|
||
import jwt | ||
from easy_profile import EasyProfileMiddleware | ||
from easy_profile.reporters import Reporter | ||
from flask import Flask, jsonify, request | ||
import bleach | ||
from opentelemetry import trace | ||
from opentelemetry import metrics, trace | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the contacts service, I tried to instrument metrics reported by the sqlalchemy-easy-profile using opentelemetry. However, the Cloud Monitoring exporter doesn't fully support all metric types yet |
||
from opentelemetry.exporter.cloud_monitoring import CloudMonitoringMetricsExporter | ||
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter | ||
from opentelemetry.exporter.cloud_trace.cloud_trace_propagator import CloudTraceFormatPropagator | ||
from opentelemetry.ext.flask import FlaskInstrumentor | ||
from opentelemetry.propagators import set_global_httptextformat | ||
from opentelemetry.sdk.metrics import Counter, MeterProvider | ||
from opentelemetry.sdk.trace import TracerProvider | ||
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor | ||
from sqlalchemy.exc import OperationalError, SQLAlchemyError | ||
from db import ContactsDb | ||
|
||
class CloudMonitoringReporter(Reporter): | ||
|
||
def __init__(self): | ||
metrics.set_meter_provider(MeterProvider()) | ||
exporter = CloudMonitoringMetricsExporter() | ||
meter = metrics.get_meter(__name__) | ||
metrics.get_meter_provider().start_pipeline(meter, exporter) | ||
|
||
self.query_count = meter.create_metric( | ||
name="query_count", | ||
description="number of queries made by each endpoint", | ||
unit="1", | ||
value_type=int, | ||
metric_type=Counter, | ||
label_keys=("endpoint"), | ||
enabled=True, | ||
) | ||
|
||
|
||
def report(self, path, stats): | ||
self.query_count.add(stats['total'], {'endpoint': path}) | ||
|
||
|
||
def create_app(): | ||
"""Flask application factory to create instances | ||
|
@@ -54,6 +80,7 @@ def create_app(): | |
|
||
# Add Flask auto-instrumentation for tracing | ||
FlaskInstrumentor().instrument_app(app) | ||
app.wsgi_app = EasyProfileMiddleware(app.wsgi_app, reporter=CloudMonitoringReporter()) | ||
|
||
# Disabling unused-variable for lines with route decorated functions | ||
# as pylint thinks they are unused | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,8 +25,13 @@ | |
|
||
import bcrypt | ||
import jwt | ||
from easy_profile import EasyProfileMiddleware | ||
from easy_profile.reporters import Reporter | ||
from flask import Flask, jsonify, request | ||
import bleach | ||
from opencensus.ext.stackdriver import stats_exporter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because opentelemetry's cloud monitoring support is incomplete, I tried using OpenCensus to export the sqlachemy metrics in the userservice, which worked as expected. |
||
from opencensus.stats import aggregation, measure, stats as oc_stats, view | ||
from opencensus.tags import tag_key, tag_map, tag_value | ||
from opentelemetry import trace | ||
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter | ||
from opentelemetry.exporter.cloud_trace.cloud_trace_propagator import CloudTraceFormatPropagator | ||
|
@@ -37,6 +42,34 @@ | |
from sqlalchemy.exc import OperationalError, SQLAlchemyError | ||
from db import UserDb | ||
|
||
class CloudMonitoringReporter(Reporter): | ||
|
||
def __init__(self): | ||
self.query_latency = measure.MeasureFloat( | ||
"query_latency", | ||
"The total latency of queries in seconds for a request", | ||
"s") | ||
|
||
self.latency_view = view.View( | ||
"query_latency_distribution", | ||
"The distribution of the query latencies", | ||
[], | ||
self.query_latency, | ||
# Latency in buckets: [>=0ms, >=1ms, >=2ms, >=4ms, >=10ms, >=20ms, >=40ms] | ||
aggregation.DistributionAggregation( | ||
[.001, .002, .004, .01, .02, .04])) | ||
|
||
oc_stats.stats.view_manager.register_view(self.latency_view) | ||
exporter = stats_exporter.new_stats_exporter() | ||
oc_stats.stats.view_manager.register_exporter(exporter) | ||
|
||
def report(self, path, stats): | ||
mmap = oc_stats.stats.stats_recorder.new_measurement_map() | ||
mmap.measure_float_put(self.query_latency, stats['duration']) | ||
tmap = tag_map.TagMap() | ||
tmap.insert(tag_key.TagKey("path"), tag_value.TagValue(path)) | ||
mmap.record(tmap) | ||
|
||
|
||
def create_app(): | ||
"""Flask application factory to create instances | ||
|
@@ -56,6 +89,9 @@ def create_app(): | |
# Add Flask auto-instrumentation for tracing | ||
FlaskInstrumentor().instrument_app(app) | ||
|
||
# automatically collect query stats for each request endpoint and send to the Reporter | ||
app.wsgi_app = EasyProfileMiddleware(app.wsgi_app, reporter=CloudMonitoringReporter()) | ||
|
||
# Disabling unused-variable for lines with route decorated functions | ||
# as pylint thinks they are unused | ||
# pylint: disable=unused-variable | ||
|
@@ -130,7 +166,7 @@ def create_user(): | |
app.logger.info("Successfully created user.") | ||
|
||
except UserWarning as warn: | ||
app.logger.error("Error creating new user: %s", str(warn)) | ||
# app.logger.error("Error creating new user: %s", str(warn)) | ||
return str(warn), 400 | ||
except NameError as err: | ||
app.logger.error("Error creating new user: %s", str(err)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As explained in this issue census-instrumentation/opencensus-python#796 (comment)
the stackdriver exporter needs these env vars explicitly declared