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

Commit cde62c5

Browse files
authored
Standard Metrics - Incoming Requests Per Second (#758)
* Incoming requests for Python3 * python2 support * change namespace * add tests * More tests * Fix lint * Fix lint * Add test * Add CHANGELOG
1 parent 479087c commit cde62c5

File tree

6 files changed

+247
-19
lines changed

6 files changed

+247
-19
lines changed

contrib/opencensus-ext-azure/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Changelog
22

33
## Unreleased
4+
- Standard metrics incoming requests per second
5+
([#758](https://github.com/census-instrumentation/opencensus-python/pull/758))
46

57
## 0.7.0
68
Released 2019-07-31

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+
- Incoming Request Rate (per second)
156157
- Outgoing Request Rate (per second)
157158
- Process CPU Usage (percentage)
158159
- Process Private Bytes (bytes)

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,24 @@
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 \
19+
from opencensus.ext.azure.metrics_exporter.standard_metrics.http_dependency \
2020
import DependencyRateMetric
2121
from opencensus.ext.azure.metrics_exporter.standard_metrics.memory \
2222
import AvailableMemoryMetric
2323
from opencensus.ext.azure.metrics_exporter.standard_metrics.process \
2424
import ProcessCPUMetric
2525
from opencensus.ext.azure.metrics_exporter.standard_metrics.process \
2626
import ProcessMemoryMetric
27+
from opencensus.ext.azure.metrics_exporter.standard_metrics.http_requests \
28+
import RequestsRateMetric
2729

2830
# List of standard metrics to track
2931
STANDARD_METRICS = [AvailableMemoryMetric,
3032
DependencyRateMetric,
3133
ProcessCPUMetric,
3234
ProcessMemoryMetric,
33-
ProcessorTimeMetric]
35+
ProcessorTimeMetric,
36+
RequestsRateMetric]
3437

3538

3639
def register_metrics():

contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/standard_metrics/dependency.py renamed to contrib/opencensus-ext-azure/opencensus/ext/azure/metrics_exporter/standard_metrics/http_dependency.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __call__(self):
7676
value over the elapsed time.
7777
7878
:rtype: :class:`opencensus.metrics.export.gauge.DerivedLongGauge`
79-
:return: The gauge representing the available memory metric
79+
:return: The gauge representing the outgoing requests metric
8080
"""
8181
gauge = DerivedDoubleGauge(
8282
DependencyRateMetric.NAME,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 sys
16+
import time
17+
18+
from opencensus.metrics.export.gauge import DerivedDoubleGauge
19+
if sys.version_info < (3,):
20+
from BaseHTTPServer import HTTPServer
21+
else:
22+
from http.server import HTTPServer
23+
24+
requests_map = dict()
25+
ORIGINAL_CONSTRUCTOR = HTTPServer.__init__
26+
27+
28+
def request_patch(func):
29+
def wrapper(self=None):
30+
func(self)
31+
count = requests_map.get('count', 0)
32+
requests_map['count'] = count + 1
33+
return wrapper
34+
35+
36+
def server_patch(*args, **kwargs):
37+
if len(args) >= 3:
38+
handler = args[2]
39+
if handler:
40+
# Patch the handler methods if they exist
41+
if "do_DELETE" in dir(handler):
42+
handler.do_DELETE = request_patch(handler.do_DELETE)
43+
if "do_GET" in dir(handler):
44+
handler.do_GET = request_patch(handler.do_GET)
45+
if "do_HEAD" in dir(handler):
46+
handler.do_HEAD = request_patch(handler.do_HEAD)
47+
if "do_OPTIONS" in dir(handler):
48+
handler.do_OPTIONS = request_patch(handler.do_OPTIONS)
49+
if "do_POST" in dir(handler):
50+
handler.do_POST = request_patch(handler.do_POST)
51+
if "do_PUT" in dir(handler):
52+
handler.do_PUT = request_patch(handler.do_PUT)
53+
result = ORIGINAL_CONSTRUCTOR(*args, **kwargs)
54+
return result
55+
56+
57+
def setup():
58+
# Patch the HTTPServer handler to track request information
59+
HTTPServer.__init__ = server_patch
60+
61+
62+
class RequestsRateMetric(object):
63+
NAME = "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec"
64+
65+
def __init__(self):
66+
setup()
67+
68+
@staticmethod
69+
def get_value():
70+
current_count = requests_map.get('count', 0)
71+
current_time = time.time()
72+
last_count = requests_map.get('last_count', 0)
73+
last_time = requests_map.get('last_time')
74+
last_result = requests_map.get('last_result', 0)
75+
76+
try:
77+
# last_time is None the very first time this function is called
78+
if last_time is not None:
79+
elapsed_seconds = current_time - last_time
80+
interval_count = current_count - last_count
81+
result = interval_count / elapsed_seconds
82+
else:
83+
result = 0
84+
requests_map['last_time'] = current_time
85+
requests_map['last_count'] = current_count
86+
requests_map['last_result'] = result
87+
return result
88+
except ZeroDivisionError:
89+
# If elapsed_seconds is 0, exporter call made too close to previous
90+
# Return the previous result if this is the case
91+
return last_result
92+
93+
def __call__(self):
94+
""" Returns a derived gauge for incoming requests per second
95+
96+
Calculated by obtaining by getting the number of incoming requests
97+
made to an HTTPServer within an elapsed time and dividing that value
98+
over the elapsed time.
99+
100+
:rtype: :class:`opencensus.metrics.export.gauge.DerivedLongGauge`
101+
:return: The gauge representing the incoming requests metric
102+
"""
103+
gauge = DerivedDoubleGauge(
104+
RequestsRateMetric.NAME,
105+
'Incoming Requests per second',
106+
'rps',
107+
[])
108+
gauge.create_default_time_series(RequestsRateMetric.get_value)
109+
return gauge

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

Lines changed: 129 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,27 @@
1515
import collections
1616
import mock
1717
import requests
18+
import sys
1819
import unittest
1920

2021
from opencensus.ext.azure.metrics_exporter import standard_metrics
2122
from opencensus.trace import execution_context
23+
if sys.version_info < (3,):
24+
from BaseHTTPServer import HTTPServer
25+
else:
26+
from http.server import HTTPServer
2227

2328
ORIGINAL_FUNCTION = requests.Session.request
29+
ORIGINAL_CONS = HTTPServer.__init__
2430

2531

2632
class TestStandardMetrics(unittest.TestCase):
2733
def setUp(self):
28-
standard_metrics.dependency.dependency_map.clear()
34+
standard_metrics.http_dependency.dependency_map.clear()
35+
standard_metrics.http_requests.requests_map.clear()
2936
requests.Session.request = ORIGINAL_FUNCTION
30-
standard_metrics.dependency.ORIGINAL_REQUEST = ORIGINAL_FUNCTION
37+
standard_metrics.http_dependency.ORIGINAL_REQUEST = ORIGINAL_FUNCTION
38+
standard_metrics.http_requests.ORIGINAL_CONSTRUCTOR = ORIGINAL_CONS
3139

3240
@mock.patch('opencensus.ext.azure.metrics_exporter'
3341
'.standard_metrics.register_metrics')
@@ -40,12 +48,12 @@ def test_producer_get_metrics(self):
4048
producer = standard_metrics.AzureStandardMetricsProducer()
4149
metrics = producer.get_metrics()
4250

43-
self.assertEqual(len(metrics), 5)
51+
self.assertEqual(len(metrics), 6)
4452

4553
def test_register_metrics(self):
4654
registry = standard_metrics.register_metrics()
4755

48-
self.assertEqual(len(registry.get_metrics()), 5)
56+
self.assertEqual(len(registry.get_metrics()), 6)
4957

5058
def test_get_available_memory_metric(self):
5159
metric = standard_metrics.AvailableMemoryMetric()
@@ -135,21 +143,21 @@ def test_get_process_cpu_usage_exception(self, logger_mock):
135143
logger_mock.exception.assert_called()
136144

137145
def test_dependency_patch(self):
138-
map = standard_metrics.dependency.dependency_map
139-
standard_metrics.dependency.ORIGINAL_REQUEST = lambda x: None
146+
map = standard_metrics.http_dependency.dependency_map
147+
standard_metrics.http_dependency.ORIGINAL_REQUEST = lambda x: None
140148
session = requests.Session()
141149
execution_context.set_is_exporter(False)
142-
result = standard_metrics.dependency.dependency_patch(session)
150+
result = standard_metrics.http_dependency.dependency_patch(session)
143151

144152
self.assertEqual(map['count'], 1)
145153
self.assertIsNone(result)
146154

147155
def test_dependency_patch_exporter_thread(self):
148-
map = standard_metrics.dependency.dependency_map
149-
standard_metrics.dependency.ORIGINAL_REQUEST = lambda x: None
156+
map = standard_metrics.http_dependency.dependency_map
157+
standard_metrics.http_dependency.ORIGINAL_REQUEST = lambda x: None
150158
session = mock.Mock()
151159
execution_context.set_is_exporter(True)
152-
result = standard_metrics.dependency.dependency_patch(session)
160+
result = standard_metrics.http_dependency.dependency_patch(session)
153161

154162
self.assertIsNone(map.get('count'))
155163
self.assertIsNone(result)
@@ -167,21 +175,126 @@ def test_get_dependency_rate_first_time(self):
167175
self.assertEqual(rate, 0)
168176

169177
@mock.patch('opencensus.ext.azure.metrics_exporter'
170-
'.standard_metrics.dependency.time')
178+
'.standard_metrics.http_dependency.time')
171179
def test_get_dependency_rate(self, time_mock):
172180
time_mock.time.return_value = 100
173-
standard_metrics.dependency.dependency_map['last_time'] = 98
174-
standard_metrics.dependency.dependency_map['count'] = 4
181+
standard_metrics.http_dependency.dependency_map['last_time'] = 98
182+
standard_metrics.http_dependency.dependency_map['count'] = 4
175183
rate = standard_metrics.DependencyRateMetric.get_value()
176184

177185
self.assertEqual(rate, 2)
178186

179187
@mock.patch('opencensus.ext.azure.metrics_exporter'
180-
'.standard_metrics.dependency.time')
188+
'.standard_metrics.http_dependency.time')
181189
def test_get_dependency_rate_error(self, time_mock):
182190
time_mock.time.return_value = 100
183-
standard_metrics.dependency.dependency_map['last_result'] = 5
184-
standard_metrics.dependency.dependency_map['last_time'] = 100
191+
standard_metrics.http_dependency.dependency_map['last_result'] = 5
192+
standard_metrics.http_dependency.dependency_map['last_time'] = 100
185193
result = standard_metrics.DependencyRateMetric.get_value()
186194

187195
self.assertEqual(result, 5)
196+
197+
def test_request_patch(self):
198+
map = standard_metrics.http_requests.requests_map
199+
func = mock.Mock()
200+
new_func = standard_metrics.http_requests.request_patch(func)
201+
new_func()
202+
203+
self.assertEqual(map['count'], 1)
204+
self.assertEqual(len(func.call_args_list), 1)
205+
206+
def test_server_patch(self):
207+
standard_metrics. \
208+
http_requests. \
209+
ORIGINAL_CONSTRUCTOR = lambda x, y, z: None
210+
with mock.patch('opencensus.ext.azure.metrics_exporter'
211+
'.standard_metrics.http_requests'
212+
'.request_patch') as request_mock:
213+
handler = mock.Mock()
214+
handler.do_DELETE.return_value = None
215+
handler.do_GET.return_value = None
216+
handler.do_HEAD.return_value = None
217+
handler.do_OPTIONS.return_value = None
218+
handler.do_POST.return_value = None
219+
handler.do_PUT.return_value = None
220+
result = standard_metrics. \
221+
http_requests. \
222+
server_patch(None, None, handler)
223+
handler.do_DELETE()
224+
handler.do_GET()
225+
handler.do_HEAD()
226+
handler.do_OPTIONS()
227+
handler.do_POST()
228+
handler.do_PUT()
229+
230+
self.assertEqual(result, None)
231+
self.assertEqual(len(request_mock.call_args_list), 6)
232+
233+
def test_server_patch_no_methods(self):
234+
standard_metrics. \
235+
http_requests. \
236+
ORIGINAL_CONSTRUCTOR = lambda x, y, z: None
237+
with mock.patch('opencensus.ext.azure.metrics_exporter'
238+
'.standard_metrics.http_requests'
239+
'.request_patch') as request_mock:
240+
handler = mock.Mock()
241+
result = standard_metrics. \
242+
http_requests. \
243+
server_patch(None, None, handler)
244+
handler.do_DELETE()
245+
handler.do_GET()
246+
handler.do_HEAD()
247+
handler.do_OPTIONS()
248+
handler.do_POST()
249+
handler.do_PUT()
250+
251+
self.assertEqual(result, None)
252+
self.assertEqual(len(request_mock.call_args_list), 0)
253+
254+
def test_server_patch_no_args(self):
255+
standard_metrics \
256+
.http_requests \
257+
.ORIGINAL_CONSTRUCTOR = lambda x, y: None
258+
r = standard_metrics.http_requests.server_patch(None, None)
259+
260+
self.assertEqual(r, None)
261+
262+
def test_server_patch_no_handler(self):
263+
standard_metrics \
264+
.http_requests \
265+
.ORIGINAL_CONSTRUCTOR = lambda x, y, z: None
266+
r = standard_metrics.http_requests.server_patch(None, None, None)
267+
268+
self.assertEqual(r, None)
269+
270+
def test_get_request_rate_metric(self):
271+
metric = standard_metrics.RequestsRateMetric()
272+
gauge = metric()
273+
274+
name = '\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec'
275+
self.assertEqual(gauge.descriptor.name, name)
276+
277+
def test_get_request_rate_first_time(self):
278+
rate = standard_metrics.RequestsRateMetric.get_value()
279+
280+
self.assertEqual(rate, 0)
281+
282+
@mock.patch('opencensus.ext.azure.metrics_exporter'
283+
'.standard_metrics.http_requests.time')
284+
def test_get_request_rate(self, time_mock):
285+
time_mock.time.return_value = 100
286+
standard_metrics.http_requests.requests_map['last_time'] = 98
287+
standard_metrics.http_requests.requests_map['count'] = 4
288+
rate = standard_metrics.RequestsRateMetric.get_value()
289+
290+
self.assertEqual(rate, 2)
291+
292+
@mock.patch('opencensus.ext.azure.metrics_exporter'
293+
'.standard_metrics.http_requests.time')
294+
def test_get_request_rate_error(self, time_mock):
295+
time_mock.time.return_value = 100
296+
standard_metrics.http_requests.requests_map['last_result'] = 5
297+
standard_metrics.http_requests.requests_map['last_time'] = 100
298+
result = standard_metrics.RequestsRateMetric.get_value()
299+
300+
self.assertEqual(result, 5)

0 commit comments

Comments
 (0)