Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit aa8846a

Browse files
lzchenreyang
authored andcommitted
Standard Metrics - Outgoing Requests Per Second (#729)
1 parent 1f92449 commit aa8846a

File tree

15 files changed

+386
-46
lines changed

15 files changed

+386
-46
lines changed

contrib/opencensus-ext-azure/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ Below is a list of standard metrics that are currently available:
153153

154154
- Available Memory (bytes)
155155
- CPU Processor Time (percentage)
156+
- Outgoing Request Rate (per second)
156157
- Process CPU Usage (percentage)
157158
- Process Private Bytes (bytes)
158159

contrib/opencensus-ext-azure/opencensus/ext/azure/common/exporter.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from opencensus.common.schedule import Queue
2020
from opencensus.common.schedule import QueueEvent
2121
from opencensus.ext.azure.common import Options
22+
from opencensus.trace import execution_context
2223

2324

2425
class BaseExporter(object):
@@ -64,6 +65,8 @@ def __init__(self, src, dst):
6465
super(Worker, self).__init__()
6566

6667
def run(self): # pragma: NO COVER
68+
# Indicate that this thread is an exporter thread.
69+
execution_context.set_is_exporter(True)
6770
src = self.src
6871
dst = self.dst
6972
while True:

contrib/opencensus-ext-azure/opencensus/ext/azure/common/transport.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
import logging
1717
import requests
1818

19-
from opencensus.trace import execution_context
20-
2119
logger = logging.getLogger(__name__)
2220

2321

@@ -42,14 +40,6 @@ def _transmit(self, envelopes):
4240
Return the next retry time in seconds for retryable failure.
4341
This function should never throw exception.
4442
"""
45-
# TODO: prevent requests being tracked
46-
blacklist_hostnames = execution_context.get_opencensus_attr(
47-
'blacklist_hostnames',
48-
)
49-
execution_context.set_opencensus_attr(
50-
'blacklist_hostnames',
51-
['dc.services.visualstudio.com'],
52-
)
5343
try:
5444
response = requests.post(
5545
url=self.options.endpoint,
@@ -64,11 +54,7 @@ def _transmit(self, envelopes):
6454
logger.warning('Transient client side error %s.', ex)
6555
# client side error (retryable)
6656
return self.options.minimum_retry_interval
67-
finally:
68-
execution_context.set_opencensus_attr(
69-
'blacklist_hostnames',
70-
blacklist_hostnames,
71-
)
57+
7258
text = 'N/A'
7359
data = None
7460
try:

contrib/opencensus-ext-azure/opencensus/ext/azure/log_exporter/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from opencensus.ext.azure.common.protocol import Message
2929
from opencensus.ext.azure.common.storage import LocalFileStorage
3030
from opencensus.ext.azure.common.transport import TransportMixin
31+
from opencensus.trace import execution_context
3132

3233
logger = logging.getLogger(__name__)
3334

@@ -76,6 +77,8 @@ def __init__(self, src, dst):
7677
)
7778

7879
def run(self):
80+
# Indicate that this thread is an exporter thread.
81+
execution_context.set_is_exporter(True)
7982
src = self._src
8083
dst = self._dst
8184
while True:

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/__init__.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from opencensus.metrics import transport
2828
from opencensus.metrics.export.metric_descriptor import MetricDescriptorType
2929
from opencensus.stats import stats as stats_module
30-
from opencensus.trace import execution_context
3130

3231
__all__ = ['MetricsExporter', 'new_metrics_exporter']
3332

@@ -127,13 +126,6 @@ def _transmit_without_retry(self, envelopes):
127126
non-retryable failure, simply outputs result to logs.
128127
This function should never throw exception.
129128
"""
130-
blacklist_hostnames = execution_context.get_opencensus_attr(
131-
'blacklist_hostnames',
132-
)
133-
execution_context.set_opencensus_attr(
134-
'blacklist_hostnames',
135-
['dc.services.visualstudio.com'],
136-
)
137129
try:
138130
response = requests.post(
139131
url=self.options.endpoint,
@@ -148,11 +140,6 @@ def _transmit_without_retry(self, envelopes):
148140
# No retry policy, log output
149141
logger.warning('Transient client side error %s.', ex)
150142
return
151-
finally:
152-
execution_context.set_opencensus_attr(
153-
'blacklist_hostnames',
154-
blacklist_hostnames,
155-
)
156143

157144
text = 'N/A'
158145
data = None

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/standard_metrics/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
from opencensus.metrics.export.metric_producer import MetricProducer
1717
from opencensus.ext.azure.metrics_exporter.standard_metrics.cpu \
1818
import ProcessorTimeMetric
19+
from opencensus.ext.azure.metrics_exporter.standard_metrics.dependency \
20+
import DependencyRateMetric
1921
from opencensus.ext.azure.metrics_exporter.standard_metrics.memory \
2022
import AvailableMemoryMetric
2123
from opencensus.ext.azure.metrics_exporter.standard_metrics.process \
@@ -24,8 +26,11 @@
2426
import ProcessMemoryMetric
2527

2628
# List of standard metrics to track
27-
STANDARD_METRICS = [AvailableMemoryMetric, ProcessCPUMetric,
28-
ProcessMemoryMetric, ProcessorTimeMetric]
29+
STANDARD_METRICS = [AvailableMemoryMetric,
30+
DependencyRateMetric,
31+
ProcessCPUMetric,
32+
ProcessMemoryMetric,
33+
ProcessorTimeMetric]
2934

3035

3136
def register_metrics():
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import requests
16+
import time
17+
18+
from opencensus.metrics.export.gauge import DerivedDoubleGauge
19+
from opencensus.trace import execution_context
20+
21+
dependency_map = dict()
22+
ORIGINAL_REQUEST = requests.Session.request
23+
24+
25+
def dependency_patch(*args, **kwargs):
26+
result = ORIGINAL_REQUEST(*args, **kwargs)
27+
# Only collect request metric if sent from non-exporter thread
28+
if not execution_context.is_exporter():
29+
count = dependency_map.get('count', 0)
30+
dependency_map['count'] = count + 1
31+
return result
32+
33+
34+
def setup():
35+
# Patch the requests library functions to track dependency information
36+
requests.Session.request = dependency_patch
37+
38+
39+
class DependencyRateMetric(object):
40+
# Dependency call metrics can be found under custom metrics
41+
NAME = "\\ApplicationInsights\\Dependency Calls/Sec"
42+
43+
def __init__(self):
44+
setup()
45+
46+
@staticmethod
47+
def get_value():
48+
current_count = dependency_map.get('count', 0)
49+
current_time = time.time()
50+
last_count = dependency_map.get('last_count', 0)
51+
last_time = dependency_map.get('last_time')
52+
last_result = dependency_map.get('last_result', 0)
53+
54+
try:
55+
# last_time is None the very first time this function is called
56+
if last_time is not None:
57+
elapsed_seconds = current_time - last_time
58+
interval_count = current_count - last_count
59+
result = interval_count / elapsed_seconds
60+
else:
61+
result = 0
62+
dependency_map['last_time'] = current_time
63+
dependency_map['last_count'] = current_count
64+
dependency_map['last_result'] = result
65+
return result
66+
except ZeroDivisionError:
67+
# If elapsed_seconds is 0, exporter call made too close to previous
68+
# Return the previous result if this is the case
69+
return last_result
70+
71+
def __call__(self):
72+
""" Returns a derived gauge for outgoing requests per second
73+
74+
Calculated by obtaining by getting the number of outgoing requests made
75+
using the requests library within an elapsed time and dividing that
76+
value over the elapsed time.
77+
78+
:rtype: :class:`opencensus.metrics.export.gauge.DerivedLongGauge`
79+
:return: The gauge representing the available memory metric
80+
"""
81+
gauge = DerivedDoubleGauge(
82+
DependencyRateMetric.NAME,
83+
'Outgoing Requests per second',
84+
'rps',
85+
[])
86+
gauge.create_default_time_series(DependencyRateMetric.get_value)
87+
return gauge

contrib/opencensus-ext-azure/tests/test_azure_standard_metrics.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,21 @@
1414

1515
import collections
1616
import mock
17+
import requests
1718
import unittest
1819

1920
from opencensus.ext.azure.metrics_exporter import standard_metrics
21+
from opencensus.trace import execution_context
22+
23+
ORIGINAL_FUNCTION = requests.Session.request
2024

2125

2226
class TestStandardMetrics(unittest.TestCase):
27+
def setUp(self):
28+
standard_metrics.dependency.dependency_map.clear()
29+
requests.Session.request = ORIGINAL_FUNCTION
30+
standard_metrics.dependency.ORIGINAL_REQUEST = ORIGINAL_FUNCTION
31+
2332
@mock.patch('opencensus.ext.azure.metrics_exporter'
2433
'.standard_metrics.register_metrics')
2534
def test_producer_ctor(self, avail_mock):
@@ -31,12 +40,12 @@ def test_producer_get_metrics(self):
3140
producer = standard_metrics.AzureStandardMetricsProducer()
3241
metrics = producer.get_metrics()
3342

34-
self.assertEqual(len(metrics), 4)
43+
self.assertEqual(len(metrics), 5)
3544

3645
def test_register_metrics(self):
3746
registry = standard_metrics.register_metrics()
3847

39-
self.assertEqual(len(registry.get_metrics()), 4)
48+
self.assertEqual(len(registry.get_metrics()), 5)
4049

4150
def test_get_available_memory_metric(self):
4251
metric = standard_metrics.AvailableMemoryMetric()
@@ -124,3 +133,55 @@ def test_get_process_cpu_usage_exception(self, logger_mock):
124133
standard_metrics.ProcessCPUMetric.get_value()
125134

126135
logger_mock.exception.assert_called()
136+
137+
def test_dependency_patch(self):
138+
map = standard_metrics.dependency.dependency_map
139+
standard_metrics.dependency.ORIGINAL_REQUEST = lambda x: None
140+
session = requests.Session()
141+
execution_context.set_is_exporter(False)
142+
result = standard_metrics.dependency.dependency_patch(session)
143+
144+
self.assertEqual(map['count'], 1)
145+
self.assertIsNone(result)
146+
147+
def test_dependency_patch_exporter_thread(self):
148+
map = standard_metrics.dependency.dependency_map
149+
standard_metrics.dependency.ORIGINAL_REQUEST = lambda x: None
150+
session = mock.Mock()
151+
execution_context.set_is_exporter(True)
152+
result = standard_metrics.dependency.dependency_patch(session)
153+
154+
self.assertIsNone(map.get('count'))
155+
self.assertIsNone(result)
156+
157+
def test_get_dependency_rate_metric(self):
158+
metric = standard_metrics.DependencyRateMetric()
159+
gauge = metric()
160+
161+
self.assertEqual(gauge.descriptor.name,
162+
'\\ApplicationInsights\\Dependency Calls/Sec')
163+
164+
def test_get_dependency_rate_first_time(self):
165+
rate = standard_metrics.DependencyRateMetric.get_value()
166+
167+
self.assertEqual(rate, 0)
168+
169+
@mock.patch('opencensus.ext.azure.metrics_exporter'
170+
'.standard_metrics.dependency.time')
171+
def test_get_dependency_rate(self, time_mock):
172+
time_mock.time.return_value = 100
173+
standard_metrics.dependency.dependency_map['last_time'] = 98
174+
standard_metrics.dependency.dependency_map['count'] = 4
175+
rate = standard_metrics.DependencyRateMetric.get_value()
176+
177+
self.assertEqual(rate, 2)
178+
179+
@mock.patch('opencensus.ext.azure.metrics_exporter'
180+
'.standard_metrics.dependency.time')
181+
def test_get_dependency_rate_error(self, time_mock):
182+
time_mock.time.return_value = 100
183+
standard_metrics.dependency.dependency_map['last_result'] = 5
184+
standard_metrics.dependency.dependency_map['last_time'] = 100
185+
result = standard_metrics.DependencyRateMetric.get_value()
186+
187+
self.assertEqual(result, 5)

contrib/opencensus-ext-httplib/opencensus/ext/httplib/trace.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ def wrap_httplib_request(request_func):
6161
"""
6262

6363
def call(self, method, url, body, headers, *args, **kwargs):
64+
# Check if request was sent from an exporter. If so, do not wrap.
65+
if execution_context.is_exporter():
66+
return request_func(self, method, url, body,
67+
headers, *args, **kwargs)
6468
_tracer = execution_context.get_opencensus_tracer()
6569
blacklist_hostnames = execution_context.get_opencensus_attr(
6670
'blacklist_hostnames')
@@ -100,6 +104,9 @@ def wrap_httplib_response(response_func):
100104
"""
101105

102106
def call(self, *args, **kwargs):
107+
# Check if request was sent from an exporter. If so, do not wrap.
108+
if execution_context.is_exporter():
109+
return response_func(self, *args, **kwargs)
103110
_tracer = execution_context.get_opencensus_tracer()
104111
current_span_id = execution_context.get_opencensus_attr(
105112
'httplib/current_span_id')

0 commit comments

Comments
 (0)