Moves and rename common/djangoapps/newrelic_custom_metrics.

This commit is contained in:
Robert Raposa
2017-03-31 09:40:36 -04:00
parent d95620775b
commit 77f111b2b1
7 changed files with 49 additions and 37 deletions

View File

@@ -0,0 +1,6 @@
This djangoapp is incorrectly named 'monitoring'.
The name is related to old functionality that used to be a part of this app.
TODO: The current contents of this app should be joined with other generic
platform utilities and renamed appropriately.

View File

@@ -0,0 +1,55 @@
"""
This is an interface to the monitoring_utils middleware. Functions
defined in this module can be used to report monitoring custom metrics.
Usage:
from openedx.core.djangoapps import monitoring_utils
...
monitoring_utils.accumulate('xb_user_state.get_many.num_items', 4)
There is no need to do anything else. The metrics are automatically cleared
before the next request.
We try to keep track of our custom metrics at:
https://openedx.atlassian.net/wiki/display/PERF/Custom+Metrics+in+New+Relic
At this time, these custom metrics will only be reported to New Relic.
TODO: supply additional public functions for storing strings and booleans.
"""
from . import middleware
def accumulate(name, value):
"""
Accumulate monitoring custom metric for the current request.
The named metric is accumulated by a numerical amount using the sum. All
metrics are queued up in the request_cache for this request. At the end of
the request, the monitoring_utils middleware will batch report all
queued accumulated metrics to the monitoring tool (e.g. New Relic).
Arguments:
name (str): The metric name. It should be period-delimited, and
increase in specificity from left to right. For example:
'xb_user_state.get_many.num_items'.
value (number): The amount to accumulate into the named metric. When
accumulate() is called multiple times for a given metric name
during a request, the sum of the values for each call is reported
for that metric. For metrics which don't make sense to accumulate,
make sure to only call this function once during a request.
"""
middleware.MonitoringCustomMetrics.accumulate_metric(name, value)
def increment(name):
"""
Increment a monitoring custom metric representing a counter.
Here we simply accumulate a new custom metric with a value of 1, and the
middleware should automatically aggregate this metric.
"""
accumulate(name, 1)

View File

@@ -0,0 +1,74 @@
"""
Middleware for handling the storage, aggregation, and reporting of custom
metrics for monitoring.
At this time, the custom metrics can only reported to New Relic.
This middleware will only call on the newrelic agent if there are any metrics
to report for this request, so it will not incur any processing overhead for
request handlers which do not record custom metrics.
"""
import logging
log = logging.getLogger(__name__)
try:
import newrelic.agent
except ImportError:
log.warning("Unable to load NewRelic agent module")
newrelic = None # pylint: disable=invalid-name
import request_cache
REQUEST_CACHE_KEY = 'monitoring_custom_metrics'
class MonitoringCustomMetrics(object):
"""
The middleware class. Make sure to add below the request cache in
MIDDLEWARE_CLASSES.
"""
@classmethod
def _get_metrics_cache(cls):
"""
Get a reference to the part of the request cache wherein we store New
Relic custom metrics related to the current request.
"""
return request_cache.get_cache(name=REQUEST_CACHE_KEY)
@classmethod
def accumulate_metric(cls, name, value):
"""
Accumulate a custom metric (name and value) in the metrics cache.
"""
metrics_cache = cls._get_metrics_cache()
metrics_cache.setdefault(name, 0)
metrics_cache[name] += value
@classmethod
def _batch_report(cls):
"""
Report the collected custom metrics to New Relic.
"""
if not newrelic:
return
metrics_cache = cls._get_metrics_cache()
for metric_name, metric_value in metrics_cache.iteritems():
newrelic.agent.add_custom_parameter(metric_name, metric_value)
# Whether or not there was an exception, report any custom NR metrics that
# may have been collected.
def process_response(self, request, response): # pylint: disable=unused-argument
"""
Django middleware handler to process a response
"""
self._batch_report()
return response
def process_exception(self, request, exception): # pylint: disable=unused-argument
"""
Django middleware handler to process an exception
"""
self._batch_report()
return None

View File

@@ -0,0 +1,47 @@
"""
Tests for monitoring custom metrics.
"""
from django.test import TestCase
from mock import patch, call
from openedx.core.djangoapps import monitoring_utils
from openedx.core.djangoapps.monitoring_utils.middleware import MonitoringCustomMetrics
class TestMonitoringCustomMetrics(TestCase):
"""
Test the monitoring_utils middleware and helpers
"""
@patch('newrelic.agent')
def test_custom_metrics_with_new_relic(self, mock_newrelic_agent):
"""
Test normal usage of collecting custom metrics and reporting to New Relic
"""
monitoring_utils.accumulate('hello', 10)
monitoring_utils.accumulate('world', 10)
monitoring_utils.accumulate('world', 10)
monitoring_utils.increment('foo')
monitoring_utils.increment('foo')
# based on the metric data above, we expect the following calls to newrelic:
nr_agent_calls_expected = [
call('hello', 10),
call('world', 20),
call('foo', 2),
]
# fake a response to trigger metrics reporting
MonitoringCustomMetrics().process_response(
'fake request',
'fake response',
)
# Assert call counts to newrelic.agent.add_custom_parameter()
expected_call_count = len(nr_agent_calls_expected)
measured_call_count = mock_newrelic_agent.add_custom_parameter.call_count
self.assertEqual(expected_call_count, measured_call_count)
# Assert call args to newrelic.agent.add_custom_parameter(). Due to
# the nature of python dicts, call order is undefined.
mock_newrelic_agent.add_custom_parameter.has_calls(nr_agent_calls_expected, any_order=True)