Add response headers for timing API requests, improve inventory script view performance.

This commit is contained in:
Chris Church
2014-05-09 15:16:34 -04:00
committed by Matthew Jones
parent 2e0ad0edbf
commit 4f74afdf19
3 changed files with 65 additions and 10 deletions

View File

@@ -5,10 +5,13 @@
import inspect import inspect
import logging import logging
import json import json
import time
# Django # Django
from django.http import Http404 from django.http import Http404
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import connection
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@@ -75,12 +78,33 @@ class APIView(views.APIView):
def initialize_request(self, request, *args, **kwargs): def initialize_request(self, request, *args, **kwargs):
''' '''
Store the Django REST Framework Request object as an attribute on the Store the Django REST Framework Request object as an attribute on the
normal Django request. normal Django request, store time the request started.
''' '''
self.time_started = time.time()
if getattr(settings, 'SQL_DEBUG', False):
self.queries_before = len(connection.queries)
drf_request = super(APIView, self).initialize_request(request, *args, **kwargs) drf_request = super(APIView, self).initialize_request(request, *args, **kwargs)
request.drf_request = drf_request request.drf_request = drf_request
return drf_request return drf_request
def finalize_response(self, request, response, *args, **kwargs):
'''
Log warning for 400 requests. Add header with elapsed time.
'''
if response.status_code >= 400:
logger.warn("status %s received by user %s attempting to access %s" % (response.status_code, request.user, request.path))
response = super(APIView, self).finalize_response(request, response, *args, **kwargs)
time_started = getattr(self, 'time_started', None)
if time_started:
time_elapsed = time.time() - self.time_started
response['X-API-Time'] = '%0.3fs' % time_elapsed
if getattr(settings, 'SQL_DEBUG', False):
queries_before = getattr(self, 'queries_before', 0)
q_times = [float(q['time']) for q in connection.queries[queries_before:]]
response['X-API-Query-Count'] = len(q_times)
response['X-API-Query-Time'] = '%0.3fs' % sum(q_times)
return response
def get_authenticate_header(self, request): def get_authenticate_header(self, request):
""" """
Determine the WWW-Authenticate header to use for 401 responses. Try to Determine the WWW-Authenticate header to use for 401 responses. Try to
@@ -134,11 +158,6 @@ class APIView(views.APIView):
ret['added_in_version'] = added_in_version ret['added_in_version'] = added_in_version
return ret return ret
def finalize_response(self, request, response, *args, **kwargs):
if response.status_code >= 400:
logger.warn("status %s received by user %s attempting to access %s" % (response.status_code, request.user, request.path))
return super(APIView, self).finalize_response(request, response, *args, **kwargs)
class GenericAPIView(generics.GenericAPIView, APIView): class GenericAPIView(generics.GenericAPIView, APIView):
# Base class for all model-based views. # Base class for all model-based views.

View File

@@ -1,6 +1,9 @@
# Copyright (c) 2014 AnsibleWorks, Inc. # Copyright (c) 2014 AnsibleWorks, Inc.
# All Rights Reserved. # All Rights Reserved.
# Django
from django.utils.datastructures import SortedDict
# Django REST Framework # Django REST Framework
from rest_framework import renderers from rest_framework import renderers
@@ -31,6 +34,15 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
if method in ('DELETE', 'OPTIONS'): if method in ('DELETE', 'OPTIONS'):
return True # Don't actually need to return a form return True # Don't actually need to return a form
def get_context(self, data, accepted_media_type, renderer_context):
context = super(BrowsableAPIRenderer, self).get_context(data, accepted_media_type, renderer_context)
# FIXME: Sort headers / preserve sorting?
#response_headers = SortedDict(context['response'].items())
#if 'Content-Type' in context['response_headers']:
# response_headers['Content-Type'] = context['response_headers']['Content-Type']
#context['response_headers'] = response_headers
return context
class PlainTextRenderer(renderers.BaseRenderer): class PlainTextRenderer(renderers.BaseRenderer):
media_type = 'text/plain' media_type = 'text/plain'

View File

@@ -947,12 +947,36 @@ class InventoryScriptView(RetrieveAPIView):
data['all'] = SortedDict() data['all'] = SortedDict()
data['all']['vars'] = self.object.variables_dict data['all']['vars'] = self.object.variables_dict
# Build in-memory mapping of groups and their hosts.
group_hosts_kw = dict(group__inventory_id=self.object.id, group__active=True,
host__inventory_id=self.object.id, host__active=True)
if 'enabled' in hosts_q:
group_hosts_kw['host__enabled'] = hosts_q['enabled']
group_hosts_qs = Group.hosts.through.objects.filter(**group_hosts_kw)
group_hosts_qs = group_hosts_qs.order_by('host__name')
group_hosts_qs = group_hosts_qs.values_list('group_id', 'host_id', 'host__name')
group_hosts_map = {}
for group_id, host_id, host_name in group_hosts_qs:
group_hostnames = group_hosts_map.setdefault(group_id, [])
group_hostnames.append(host_name)
# Build in-memory mapping of groups and their children.
group_parents_qs = Group.parents.through.objects.filter(
from_group__inventory_id=self.object.id, from_group__active=True,
to_group__inventory_id=self.object.id, to_group__active=True,
)
group_parents_qs = group_parents_qs.order_by('from_group__name')
group_parents_qs = group_parents_qs.values_list('from_group_id', 'from_group__name', 'to_group_id')
group_children_map = {}
for from_group_id, from_group_name, to_group_id in group_parents_qs:
group_children = group_children_map.setdefault(to_group_id, [])
group_children.append(from_group_name)
# Now use in-memory maps to build up group info.
for group in self.object.groups.filter(active=True): for group in self.object.groups.filter(active=True):
hosts = group.hosts.filter(**hosts_q)
children = group.children.filter(active=True)
group_info = SortedDict() group_info = SortedDict()
group_info['hosts'] = list(hosts.values_list('name', flat=True)) group_info['hosts'] = group_hosts_map.get(group.id, [])
group_info['children'] = list(children.values_list('name', flat=True)) group_info['children'] = group_children_map.get(group.id, [])
group_info['vars'] = group.variables_dict group_info['vars'] = group.variables_dict
data[group.name] = group_info data[group.name] = group_info