From b4832098626d3f7169a0dc31050209a45891c41d Mon Sep 17 00:00:00 2001 From: Matt Jibson <matt.jibson@gmail.com> Date: Wed, 12 Aug 2015 11:58:04 -0400 Subject: [PATCH] Wrap API and registry requests with common metric timings Record response times, codes, and rollup non-2XX responses. --- endpoints/api/__init__.py | 5 +++-- endpoints/v1/__init__.py | 3 +++ endpoints/v2/__init__.py | 4 +++- util/saas/metricqueue.py | 40 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index fc16723d1..d8c2a9e66 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -1,7 +1,7 @@ import logging import datetime -from app import app +from app import app, metric_queue from flask import Blueprint, request, make_response, jsonify, session from flask.ext.restful import Resource, abort, Api, reqparse from flask.ext.restful.utils.cors import crossdomain @@ -20,6 +20,7 @@ from auth.auth_context import get_authenticated_user, get_validated_oauth_token from auth.auth import process_oauth from endpoints.csrf import csrf_protect from endpoints.decorators import check_anon_protection +from util.saas.metricqueue import time_decorator logger = logging.getLogger(__name__) @@ -28,7 +29,7 @@ api = Api() api.init_app(api_bp) api.decorators = [csrf_protect, crossdomain(origin='*', headers=['Authorization', 'Content-Type']), - process_oauth] + process_oauth, time_decorator(api_bp.name, metric_queue)] class ApiException(Exception): diff --git a/endpoints/v1/__init__.py b/endpoints/v1/__init__.py index 587d0dbec..3eabbe338 100644 --- a/endpoints/v1/__init__.py +++ b/endpoints/v1/__init__.py @@ -1,10 +1,13 @@ from flask import Blueprint, make_response +from app import metric_queue from endpoints.decorators import anon_protect, anon_allowed +from util.saas.metricqueue import time_blueprint v1_bp = Blueprint('v1', __name__) +time_blueprint(v1_bp, metric_queue) # Note: This is *not* part of the Docker index spec. This is here for our own health check, # since we have nginx handle the _ping below. diff --git a/endpoints/v2/__init__.py b/endpoints/v2/__init__.py index 0de3dc539..c1c7c22c0 100644 --- a/endpoints/v2/__init__.py +++ b/endpoints/v2/__init__.py @@ -7,6 +7,7 @@ from flask import Blueprint, make_response, url_for, request from functools import wraps from urlparse import urlparse +from app import metric_queue from endpoints.decorators import anon_protect, anon_allowed from auth.jwt_auth import process_jwt_auth from auth.auth_context import get_grant_user_context @@ -14,12 +15,13 @@ from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermissi AdministerRepositoryPermission) from data import model from util.http import abort - +from util.saas.metricqueue import time_blueprint logger = logging.getLogger(__name__) v2_bp = Blueprint('v2', __name__) +time_blueprint(v2_bp, metric_queue) def _require_repo_permission(permission_class, allow_public=False): def wrapper(func): diff --git a/util/saas/metricqueue.py b/util/saas/metricqueue.py index 1405f384c..f9e8efe13 100644 --- a/util/saas/metricqueue.py +++ b/util/saas/metricqueue.py @@ -1,7 +1,11 @@ import logging +import time +from functools import wraps from Queue import Queue, Full +from flask import g, request + logger = logging.getLogger(__name__) @@ -16,4 +20,38 @@ class MetricQueue(object): logger.error('Metric queue full') def get(self): - return self._queue.get() + v = self._queue.get() + return v + +def time_blueprint(bp, metric_queue): + bp.before_request(time_before_request) + bp.after_request(time_after_request(bp.name, metric_queue)) + +def time_before_request(): + g._start = time.time() + +def time_after_request(name, metric_queue): + def f(r): + start = getattr(g, '_start', None) + if start is None: + return r + dur = time.time() - start + dims = dimensions={'endpoint': request.endpoint} + metric_queue.put('ResponseTime', dur, dimensions=dims, unit='Seconds') + metric_queue.put('ResponseCode', r.status_code, dimensions=dims) + if r.status_code < 200 or r.status_code >= 300: + metric_queue.put('Non200Response', 1, dimensions={'name': name}) + return r + return f + +def time_decorator(name, metric_queue): + after = time_after_request(name, metric_queue) + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + time_before_request() + rv = func(*args, **kwargs) + after(rv) + return rv + return wrapper + return decorator