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

Commit 8e582cb

Browse files
authored
Standard Metrics - Incoming Requests Execution Time (#773)
* Incoming Requests Execution Time * Fix test * Remove blank line * ADd line * Update comments * Include locks to prevent race case * Fix lint * address comments
1 parent a8c3415 commit 8e582cb

File tree

6 files changed

+138
-35
lines changed

6 files changed

+138
-35
lines changed

contrib/opencensus-ext-azure/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Unreleased
44
- Standard metrics incoming requests per second
55
([#758](https://github.com/census-instrumentation/opencensus-python/pull/758))
6+
- Standard metrics incoming requests average execution rate
7+
([#773](https://github.com/census-instrumentation/opencensus-python/pull/773))
68

79
## 0.7.0
810
Released 2019-07-31

contrib/opencensus-ext-azure/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ Below is a list of standard metrics that are currently available:
154154
- Available Memory (bytes)
155155
- CPU Processor Time (percentage)
156156
- Incoming Request Rate (per second)
157+
- Incoming Request Average Execution Time (milliseconds)
157158
- Outgoing Request Rate (per second)
158159
- Process CPU Usage (percentage)
159160
- Process Private Bytes (bytes)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
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 RequestsAvgExecutionMetric
2729
from opencensus.ext.azure.metrics_exporter.standard_metrics.http_requests \
2830
import RequestsRateMetric
2931

@@ -33,6 +35,7 @@
3335
ProcessCPUMetric,
3436
ProcessMemoryMetric,
3537
ProcessorTimeMetric,
38+
RequestsAvgExecutionMetric,
3639
RequestsRateMetric]
3740

3841

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,25 @@
1313
# limitations under the License.
1414

1515
import requests
16+
import threading
1617
import time
1718

1819
from opencensus.metrics.export.gauge import DerivedDoubleGauge
1920
from opencensus.trace import execution_context
2021

2122
dependency_map = dict()
23+
_dependency_lock = threading.Lock()
2224
ORIGINAL_REQUEST = requests.Session.request
2325

2426

2527
def dependency_patch(*args, **kwargs):
2628
result = ORIGINAL_REQUEST(*args, **kwargs)
2729
# Only collect request metric if sent from non-exporter thread
2830
if not execution_context.is_exporter():
29-
count = dependency_map.get('count', 0)
30-
dependency_map['count'] = count + 1
31+
# We don't want multiple threads updating this at once
32+
with _dependency_lock:
33+
count = dependency_map.get('count', 0)
34+
dependency_map['count'] = count + 1
3135
return result
3236

3337

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

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import sys
16+
import threading
1617
import time
1718

1819
from opencensus.metrics.export.gauge import DerivedDoubleGauge
@@ -21,16 +22,32 @@
2122
else:
2223
from http.server import HTTPServer
2324

25+
_requests_lock = threading.Lock()
2426
requests_map = dict()
2527
ORIGINAL_CONSTRUCTOR = HTTPServer.__init__
2628

2729

2830
def request_patch(func):
2931
def wrapper(self=None):
32+
start_time = time.time()
3033
func(self)
34+
end_time = time.time()
35+
36+
update_request_state(start_time, end_time)
37+
38+
return wrapper
39+
40+
41+
def update_request_state(start_time, end_time):
42+
# Update requests state information
43+
# We don't want multiple threads updating this at once
44+
with _requests_lock:
45+
# Update Count
3146
count = requests_map.get('count', 0)
3247
requests_map['count'] = count + 1
33-
return wrapper
48+
# Update duration
49+
duration = requests_map.get('duration', 0)
50+
requests_map['duration'] = duration + (end_time - start_time)
3451

3552

3653
def server_patch(*args, **kwargs):
@@ -59,6 +76,76 @@ def setup():
5976
HTTPServer.__init__ = server_patch
6077

6178

79+
def get_average_execution_time():
80+
last_average_duration = requests_map.get('last_average_duration', 0)
81+
interval_duration = requests_map.get('duration', 0) \
82+
- requests_map.get('last_duration', 0)
83+
interval_count = requests_map.get('count', 0) \
84+
- requests_map.get('last_count', 0)
85+
try:
86+
result = interval_duration / interval_count
87+
requests_map['last_average_duration'] = result
88+
requests_map['last_duration'] = requests_map.get('duration', 0)
89+
# Convert to milliseconds
90+
return result * 1000.0
91+
except ZeroDivisionError:
92+
# If interval_count is 0, exporter call made too close to previous
93+
# Return the previous result if this is the case
94+
return last_average_duration * 1000.0
95+
96+
97+
def get_requests_rate():
98+
current_time = time.time()
99+
last_rate = requests_map.get('last_rate', 0)
100+
last_time = requests_map.get('last_time')
101+
102+
try:
103+
# last_rate_time is None the first time this function is called
104+
if last_time is not None:
105+
interval_time = current_time - requests_map.get('last_time', 0)
106+
interval_count = requests_map.get('count', 0) \
107+
- requests_map.get('last_count', 0)
108+
result = interval_count / interval_time
109+
else:
110+
result = 0
111+
requests_map['last_time'] = current_time
112+
requests_map['last_count'] = requests_map.get('count', 0)
113+
requests_map['last_rate'] = result
114+
return result
115+
except ZeroDivisionError:
116+
# If elapsed_seconds is 0, exporter call made too close to previous
117+
# Return the previous result if this is the case
118+
return last_rate
119+
120+
121+
class RequestsAvgExecutionMetric(object):
122+
NAME = "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time"
123+
124+
def __init__(self):
125+
setup()
126+
127+
@staticmethod
128+
def get_value():
129+
return get_average_execution_time()
130+
131+
def __call__(self):
132+
""" Returns a derived gauge for incoming requests execution rate
133+
134+
Calculated by getting the time it takes to make an incoming request
135+
and dividing over the amount of incoming requests over an elapsed time.
136+
137+
:rtype: :class:`opencensus.metrics.export.gauge.DerivedLongGauge`
138+
:return: The gauge representing the incoming requests metric
139+
"""
140+
gauge = DerivedDoubleGauge(
141+
RequestsAvgExecutionMetric.NAME,
142+
'Incoming Requests Average Execution Rate',
143+
'milliseconds',
144+
[])
145+
gauge.create_default_time_series(RequestsAvgExecutionMetric.get_value)
146+
return gauge
147+
148+
62149
class RequestsRateMetric(object):
63150
NAME = "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec"
64151

@@ -67,28 +154,7 @@ def __init__(self):
67154

68155
@staticmethod
69156
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
157+
return get_requests_rate()
92158

93159
def __call__(self):
94160
""" Returns a derived gauge for incoming requests per second

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

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ def test_producer_get_metrics(self):
4848
producer = standard_metrics.AzureStandardMetricsProducer()
4949
metrics = producer.get_metrics()
5050

51-
self.assertEqual(len(metrics), 6)
51+
self.assertEqual(len(metrics), 7)
5252

5353
def test_register_metrics(self):
5454
registry = standard_metrics.register_metrics()
5555

56-
self.assertEqual(len(registry.get_metrics()), 6)
56+
self.assertEqual(len(registry.get_metrics()), 7)
5757

5858
def test_get_available_memory_metric(self):
5959
metric = standard_metrics.AvailableMemoryMetric()
@@ -201,6 +201,7 @@ def test_request_patch(self):
201201
new_func()
202202

203203
self.assertEqual(map['count'], 1)
204+
self.assertIsNotNone(map['duration'])
204205
self.assertEqual(len(func.call_args_list), 1)
205206

206207
def test_server_patch(self):
@@ -267,34 +268,60 @@ def test_server_patch_no_handler(self):
267268

268269
self.assertEqual(r, None)
269270

270-
def test_get_request_rate_metric(self):
271+
def test_get_requests_rate_metric(self):
271272
metric = standard_metrics.RequestsRateMetric()
272273
gauge = metric()
273274

274275
name = '\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec'
275276
self.assertEqual(gauge.descriptor.name, name)
276277

277-
def test_get_request_rate_first_time(self):
278-
rate = standard_metrics.RequestsRateMetric.get_value()
278+
def test_get_requests_rate_first_time(self):
279+
rate = standard_metrics.http_requests.get_requests_rate()
279280

280281
self.assertEqual(rate, 0)
281282

282283
@mock.patch('opencensus.ext.azure.metrics_exporter'
283284
'.standard_metrics.http_requests.time')
284-
def test_get_request_rate(self, time_mock):
285+
def test_get_requests_rate(self, time_mock):
285286
time_mock.time.return_value = 100
286287
standard_metrics.http_requests.requests_map['last_time'] = 98
287288
standard_metrics.http_requests.requests_map['count'] = 4
288-
rate = standard_metrics.RequestsRateMetric.get_value()
289+
rate = standard_metrics.http_requests.get_requests_rate()
289290

290291
self.assertEqual(rate, 2)
291292

292293
@mock.patch('opencensus.ext.azure.metrics_exporter'
293294
'.standard_metrics.http_requests.time')
294-
def test_get_request_rate_error(self, time_mock):
295+
def test_get_requests_rate_error(self, time_mock):
295296
time_mock.time.return_value = 100
296-
standard_metrics.http_requests.requests_map['last_result'] = 5
297+
standard_metrics.http_requests.requests_map['last_rate'] = 5
297298
standard_metrics.http_requests.requests_map['last_time'] = 100
298-
result = standard_metrics.RequestsRateMetric.get_value()
299+
result = standard_metrics.http_requests.get_requests_rate()
299300

300301
self.assertEqual(result, 5)
302+
303+
def test_get_requests_execution_metric(self):
304+
metric = standard_metrics.RequestsAvgExecutionMetric()
305+
gauge = metric()
306+
307+
name = '\\ASP.NET Applications(??APP_W3SVC_PROC??)' \
308+
'\\Request Execution Time'
309+
self.assertEqual(gauge.descriptor.name, name)
310+
311+
def test_get_requests_execution(self):
312+
map = standard_metrics.http_requests.requests_map
313+
map['duration'] = 0.1
314+
map['count'] = 10
315+
map['last_count'] = 5
316+
result = standard_metrics.http_requests.get_average_execution_time()
317+
318+
self.assertEqual(result, 20)
319+
320+
def test_get_requests_execution_error(self):
321+
map = standard_metrics.http_requests.requests_map
322+
map['duration'] = 0.1
323+
map['count'] = 10
324+
map['last_count'] = 10
325+
result = standard_metrics.http_requests.get_average_execution_time()
326+
327+
self.assertEqual(result, 0)

0 commit comments

Comments
 (0)