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