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

Commit ff4c792

Browse files
lzchenreyang
authored andcommitted
Standard Metrics - Process CPU Usage (#722)
1 parent 0d00458 commit ff4c792

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
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+
- Process CPU Usage (percentage)
156157
- Process private bytes (bytes)
157158

158159
Trace

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
from opencensus.metrics.export.metric_producer import MetricProducer
2222

2323
logger = logging.getLogger(__name__)
24-
24+
PROCESS = psutil.Process()
2525

2626
# Namespaces used in Azure Monitor
2727
AVAILABLE_MEMORY = "\\Memory\\Available Bytes"
2828
PRIVATE_BYTES = "\\Process(??APP_WIN32_PROC??)\\Private Bytes"
2929
PROCESSOR_TIME = "\\Processor(_Total)\\% Processor Time"
30+
PROCESS_TIME = "\\Process(??APP_WIN32_PROC??)\\% Processor Time"
3031

3132

3233
def get_available_memory():
@@ -53,8 +54,7 @@ def get_available_memory_metric():
5354

5455
def get_process_private_bytes():
5556
try:
56-
process = psutil.Process()
57-
return process.memory_info().rss
57+
return PROCESS.memory_info().rss
5858
except Exception:
5959
logger.exception('Error handling get process private bytes.')
6060

@@ -77,6 +77,39 @@ def get_process_private_bytes_metric():
7777
return gauge
7878

7979

80+
def get_process_cpu_usage():
81+
try:
82+
# In the case of a process running on multiple threads on different CPU
83+
# cores, the returned value of cpu_percent() can be > 100.0. We
84+
# normalize the cpu process using the number of logical CPUs
85+
cpu_count = psutil.cpu_count(logical=True)
86+
return PROCESS.cpu_percent() / cpu_count
87+
except Exception:
88+
logger.exception('Error handling get process cpu usage.')
89+
90+
91+
def get_process_cpu_usage_metric():
92+
""" Returns a derived gauge for the CPU usage for the current process.
93+
94+
Return values range from 0.0 to 100.0 inclusive.
95+
96+
:rtype: :class:`opencensus.metrics.export.gauge.DerivedDoubleGauge`
97+
:return: The gauge representing the process cpu usage metric
98+
"""
99+
gauge = DerivedDoubleGauge(
100+
PROCESS_TIME,
101+
'Processor time as a percentage',
102+
'percentage',
103+
[])
104+
gauge.create_default_time_series(get_process_cpu_usage)
105+
# From the psutil docs: the first time this method is called with interval
106+
# = None it will return a meaningless 0.0 value which you are supposed to
107+
# ignore. Call cpu_percent() with process once so that the subsequent calls
108+
# from the gauge will be meaningful.
109+
PROCESS.cpu_percent()
110+
return gauge
111+
112+
80113
def get_processor_time():
81114
cpu_times_percent = psutil.cpu_times_percent()
82115
return 100 - cpu_times_percent.idle
@@ -117,6 +150,7 @@ def __init__(self):
117150
self.registry = Registry()
118151
self.registry.add_gauge(get_available_memory_metric())
119152
self.registry.add_gauge(get_process_private_bytes_metric())
153+
self.registry.add_gauge(get_process_cpu_usage_metric())
120154
self.registry.add_gauge(get_processor_time_metric())
121155

122156
def get_metrics(self):

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_producer_get_metrics(self):
3131
producer = standard_metrics.AzureStandardMetricsProducer()
3232
metrics = producer.get_metrics()
3333

34-
self.assertEqual(len(metrics), 3)
34+
self.assertEqual(len(metrics), 4)
3535

3636
def test_get_available_memory_metric(self):
3737
gauge = standard_metrics.get_available_memory_metric()
@@ -55,21 +55,21 @@ def test_get_process_private_bytes_metric(self):
5555
'\\Process(??APP_WIN32_PROC??)\\Private Bytes')
5656

5757
def test_get_process_private_bytes(self):
58-
with mock.patch('psutil.Process') as process_mock:
58+
with mock.patch('opencensus.ext.azure.metrics_exporter' +
59+
'.standard_metrics.PROCESS') as process_mock:
5960
memory = collections.namedtuple('memory', 'rss')
6061
pmem = memory(rss=100)
61-
process = mock.Mock()
62-
process.memory_info.return_value = pmem
63-
process_mock.return_value = process
62+
process_mock.memory_info.return_value = pmem
6463
mem = standard_metrics.get_process_private_bytes()
6564

6665
self.assertEqual(mem, 100)
6766

6867
@mock.patch('opencensus.ext.azure.metrics_exporter'
6968
'.standard_metrics.logger')
7069
def test_get_process_private_bytes_exception(self, logger_mock):
71-
with mock.patch('psutil.Process') as process_mock:
72-
process_mock.side_effect = Exception()
70+
with mock.patch('opencensus.ext.azure.metrics_exporter' +
71+
'.standard_metrics.PROCESS') as process_mock:
72+
process_mock.memory_info.side_effect = Exception()
7373
standard_metrics.get_process_private_bytes()
7474

7575
logger_mock.exception.assert_called()
@@ -88,3 +88,30 @@ def test_get_processor_time(self):
8888
processor_time = standard_metrics.get_processor_time()
8989

9090
self.assertEqual(processor_time, 5.5)
91+
92+
def test_get_process_cpu_usage_metric(self):
93+
gauge = standard_metrics.get_process_cpu_usage_metric()
94+
95+
self.assertEqual(gauge.descriptor.name,
96+
'\\Process(??APP_WIN32_PROC??)\\% Processor Time')
97+
98+
def test_get_process_cpu_usage(self):
99+
with mock.patch('opencensus.ext.azure.metrics_exporter' +
100+
'.standard_metrics.PROCESS') as process_mock:
101+
with mock.patch('opencensus.ext.azure.metrics_exporter' +
102+
'.standard_metrics.psutil') as psutil_mock:
103+
process_mock.cpu_percent.return_value = 44.4
104+
psutil_mock.cpu_count.return_value = 2
105+
cpu_usage = standard_metrics.get_process_cpu_usage()
106+
107+
self.assertEqual(cpu_usage, 22.2)
108+
109+
@mock.patch('opencensus.ext.azure.metrics_exporter'
110+
'.standard_metrics.logger')
111+
def test_get_process_cpu_usage_exception(self, logger_mock):
112+
with mock.patch('opencensus.ext.azure.metrics_exporter' +
113+
'.standard_metrics.psutil') as psutil_mock:
114+
psutil_mock.cpu_count.return_value = None
115+
standard_metrics.get_process_cpu_usage()
116+
117+
logger_mock.exception.assert_called()

0 commit comments

Comments
 (0)