Skip to content

Commit 32d0235

Browse files
authored
Fix/CFMTech#60 : Allow to set CPU frequency externally
Allow to set the CPU frequency from an environment variable. Additionally, whenever psutil cannot resolve the cpu frequency, we use the same fallback mechanism. Added environment variables: - PYTEST_MONITOR_FORCE_CPU_FREQ (to overcome psutil) - PYTEST_MONITOR_CPU_FREQ (to set a CPU frequency value)
1 parent 7e68f89 commit 32d0235

File tree

6 files changed

+152
-35
lines changed

6 files changed

+152
-35
lines changed

docs/sources/changelog.rst

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
Changelog
33
=========
44

5+
* :release:`1.6.5 <2022-10-16>`
6+
* :bug:`#60` Make sure that when psutil cannot fetch cpu frequency, the fallback mechanism is used.
7+
58
* :release:`1.6.4 <2022-05-18>`
69
* :bug:`#56` Force the CPU frequency to 0 and emit a warning when unable to fetch it from the system.
710
* :bug:`#54` Fix a bug that crashes the monitor upon non ASCII characters in commit log under Perforce. Improved P4 change number extraction.

docs/sources/configuration.rst

+16
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,19 @@ garbage collector, you just have to set the option `--no-gc` on the command line
176176
177177
bash $> pytest --no-gc
178178
179+
Forcing CPU frequency
180+
---------------------
181+
Under some circumstances, you may want to set the CPU frequency instead of asking `pytest-monitor` to compute it.
182+
To do so, you can either:
183+
- ask `pytest-monitor` to use a preset value if it does not manage to compute the CPU frequency
184+
- or to not try computing the CPU frequency and use your preset value.
185+
186+
Two environment variables controls this behaviour:
187+
- `PYTEST_MONITOR_CPU_FREQ` allows you to preset a value for the CPU frequency. It must be a float convertible value.
188+
This value will be used if `pytest-monitor` cannot compute the CPU frequency. Otherwise, `0.0` will be used as a
189+
default value.
190+
- `PYTEST_MONITOR_FORCE_CPU_FREQ` instructs `pytest-monitor` to try computing the CPU frequency or not. It expects an
191+
integer convertible value. If not set, or if the integer representation of the value is `0`, then `pytest-monitor` will
192+
try to compute the cpu frequency and defaults to the usecase describe for the previous environment variable.
193+
If it set and not equal to `0`, then we use the value that the environment variable `PYTEST_MONITOR_CPU_FREQ` holds
194+
(`0.0` if not set).

docs/sources/installation.rst

+1-6
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,8 @@ Supported environments
1111

1212
**You will need pytest 4.4+ to run pytest-monitor.**
1313

14-
The following versions of Python are supported:
14+
We support all versions of Python >= 3.6.
1515

16-
- Python 3.5
17-
- Python 3.6
18-
- Python 3.7
19-
20-
Support for Python 3.8 is still experimental.
2116

2217
From conda
2318
----------

pytest_monitor/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = "1.6.4"
1+
__version__ = "1.6.5"
22
__author__ = "Jean-Sebastien Dieu"

pytest_monitor/sys_utils.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,14 @@ class ExecutionContext:
6868
def __init__(self):
6969
self.__cpu_count = multiprocessing.cpu_count()
7070
self.__cpu_vendor = _get_cpu_string()
71-
try:
72-
self.__cpu_freq_base = psutil.cpu_freq().current
73-
except AttributeError:
74-
warnings.warn("Unable to fetch CPU frequency. Forcing it to 0.")
75-
self.__cpu_freq_base = 0
71+
if int(os.environ.get('PYTEST_MONITOR_FORCE_CPU_FREQ', '0')):
72+
self._read_cpu_freq_from_env()
73+
else:
74+
try:
75+
self.__cpu_freq_base = psutil.cpu_freq().current
76+
except (AttributeError, NotImplementedError, FileNotFoundError):
77+
warnings.warn("Unable to fetch CPU frequency. Trying to read it from environment..")
78+
self._read_cpu_freq_from_env()
7679
self.__proc_typ = platform.processor()
7780
self.__tot_mem = int(psutil.virtual_memory().total / 1024**2)
7881
self.__fqdn = socket.getfqdn()
@@ -81,6 +84,13 @@ def __init__(self):
8184
self.__system = '{} - {}'.format(platform.system(), platform.release())
8285
self.__py_ver = sys.version
8386

87+
def _read_cpu_freq_from_env(self):
88+
try:
89+
self.__cpu_freq_base = float(os.environ.get('PYTEST_MONITOR_CPU_FREQ', '0.'))
90+
except (ValueError, TypeError):
91+
warnings.warn("Wrong type/value while reading cpu frequency from environment. Forcing to 0.0.")
92+
self.__cpu_freq_base = 0.0
93+
8494
def to_dict(self):
8595
return dict(cpu_count=self.cpu_count,
8696
cpu_frequency=self.cpu_frequency,

tests/test_monitor_context.py

+116-23
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,135 @@
11
import mock
2+
import os
23
import pathlib
4+
import pytest
35
import sqlite3
46

57

6-
@mock.patch('pytest_monitor.sys_utils.psutil.cpu_freq', return_value=None)
7-
def test_when_cpu_freq_cannot_fetch_frequency(cpu_freq_mock, testdir):
8-
"""Make sure that pytest-monitor does the job when we have issue in collecing context resources"""
8+
CPU_FREQ_PATH = 'pytest_monitor.sys_utils.psutil.cpu_freq'
9+
10+
TEST_CONTENT = """
11+
import time
12+
13+
14+
def test_ok():
15+
time.sleep(0.5)
16+
x = ['a' * i for i in range(100)]
17+
assert len(x) == 100
18+
"""
19+
20+
def get_nb_metrics_with_cpu_freq(path):
21+
pymon_path = pathlib.Path(str(path)) / '.pymon'
22+
db = sqlite3.connect(path.as_posix())
23+
cursor = db.cursor()
24+
cursor.execute('SELECT ITEM FROM TEST_METRICS;')
25+
nb_metrics = len(cursor.fetchall())
26+
cursor = db.cursor()
27+
cursor.execute('SELECT CPU_FREQUENCY_MHZ FROM EXECUTION_CONTEXTS;')
28+
rows = cursor.fetchall()
29+
assert 1 == len(rows)
30+
cpu_freq = rows[0][0]
31+
return nb_metrics, cpu_freq
32+
33+
34+
def test_force_cpu_freq_set_0_use_psutil(testdir):
35+
"""Test that when force mode is set, we do not call psutil to fetch CPU's frequency"""
36+
937
# create a temporary pytest test module
10-
testdir.makepyfile("""
11-
import time
38+
testdir.makepyfile(TEST_CONTENT)
1239

40+
with mock.patch(CPU_FREQ_PATH, return_value=1500) as cpu_freq_mock:
41+
os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ'] = '0'
42+
os.environ['PYTEST_MONITOR_CPU_FREQ'] = '3000'
43+
# run pytest with the following cmd args
44+
result = testdir.runpytest('-vv')
45+
del os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ']
46+
del os.environ['PYTEST_MONITOR_CPU_FREQ']
47+
cpu_freq_mock.assert_called()
1348

14-
def test_ok():
15-
time.sleep(0.5)
16-
x = ['a' * i for i in range(100)]
17-
assert len(x) == 100
49+
# fnmatch_lines does an assertion internally
50+
result.stdout.fnmatch_lines(['*::test_ok PASSED*'])
51+
# make sure that that we get a '0' exit code for the test suite
52+
result.assert_outcomes(passed=1)
1853

19-
""")
54+
assert 1, 3000 == get_nb_metrics_with_cpu_freq(testdir)
2055

21-
# run pytest with the following cmd args
22-
result = testdir.runpytest('-vv')
56+
57+
def test_force_cpu_freq(testdir):
58+
"""Test that when force mode is set, we do not call psutil to fetch CPU's frequency"""
59+
60+
# create a temporary pytest test module
61+
testdir.makepyfile(TEST_CONTENT)
62+
63+
with mock.patch(CPU_FREQ_PATH, return_value=1500) as cpu_freq_mock:
64+
os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ'] = '1'
65+
os.environ['PYTEST_MONITOR_CPU_FREQ'] = '3000'
66+
# run pytest with the following cmd args
67+
result = testdir.runpytest('-vv')
68+
del os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ']
69+
del os.environ['PYTEST_MONITOR_CPU_FREQ']
70+
cpu_freq_mock.assert_not_called()
2371

2472
# fnmatch_lines does an assertion internally
2573
result.stdout.fnmatch_lines(['*::test_ok PASSED*'])
74+
# make sure that that we get a '0' exit code for the test suite
75+
result.assert_outcomes(passed=1)
76+
77+
assert 1, 3000 == get_nb_metrics_with_cpu_freq(testdir)
78+
79+
80+
@pytest.mark.parametrize('effect', [AttributeError, NotImplementedError, FileNotFoundError])
81+
def test_when_cpu_freq_cannot_fetch_frequency_set_freq_by_using_fallback(effect, testdir):
82+
"""Make sure that pytest-monitor fallback takes value of CPU FREQ from special env var"""
83+
# create a temporary pytest test module
84+
testdir.makepyfile(TEST_CONTENT)
2685

27-
pymon_path = pathlib.Path(str(testdir)) / '.pymon'
28-
assert pymon_path.exists()
86+
with mock.patch(CPU_FREQ_PATH, side_effect=effect) as cpu_freq_mock:
87+
os.environ['PYTEST_MONITOR_CPU_FREQ'] = '3000'
88+
# run pytest with the following cmd args
89+
result = testdir.runpytest('-vv')
90+
del os.environ['PYTEST_MONITOR_CPU_FREQ']
91+
cpu_freq_mock.assert_called()
2992

93+
# fnmatch_lines does an assertion internally
94+
result.stdout.fnmatch_lines(['*::test_ok PASSED*'])
3095
# make sure that that we get a '0' exit code for the test suite
3196
result.assert_outcomes(passed=1)
3297

33-
db = sqlite3.connect(str(pymon_path))
34-
cursor = db.cursor()
35-
cursor.execute('SELECT ITEM FROM TEST_METRICS;')
36-
assert 1 == len(cursor.fetchall()) # current test
37-
cursor = db.cursor()
38-
cursor.execute('SELECT CPU_FREQUENCY_MHZ FROM EXECUTION_CONTEXTS;')
39-
rows = cursor.fetchall()
40-
assert 1 == len(rows)
41-
assert rows[0][0] == 0
98+
assert 1, 3000 == get_nb_metrics_with_cpu_freq(testdir)
99+
100+
101+
@pytest.mark.parametrize('effect', [AttributeError, NotImplementedError, FileNotFoundError])
102+
def test_when_cpu_freq_cannot_fetch_frequency_set_freq_to_0(effect, testdir):
103+
"""Make sure that pytest-monitor's fallback mechanism is efficient enough. """
104+
# create a temporary pytest test module
105+
testdir.makepyfile(TEST_CONTENT)
106+
107+
with mock.patch(CPU_FREQ_PATH, side_effect=effect) as cpu_freq_mock:
108+
# run pytest with the following cmd args
109+
result = testdir.runpytest('-vv')
110+
cpu_freq_mock.assert_called()
111+
112+
# fnmatch_lines does an assertion internally
113+
result.stdout.fnmatch_lines(['*::test_ok PASSED*'])
114+
# make sure that that we get a '0' exit code for the test suite
115+
result.assert_outcomes(passed=1)
116+
117+
assert 1, 0 == get_nb_metrics_with_cpu_freq(testdir)
42118

119+
120+
@mock.patch('pytest_monitor.sys_utils.psutil.cpu_freq', return_value=None)
121+
def test_when_cpu_freq_cannot_fetch_frequency(cpu_freq_mock, testdir):
122+
"""Make sure that pytest-monitor does the job when we have issue in collecing context resources"""
123+
# create a temporary pytest test module
124+
testdir.makepyfile(TEST_CONTENT)
125+
126+
# run pytest with the following cmd args
127+
result = testdir.runpytest('-vv')
128+
129+
# fnmatch_lines does an assertion internally
130+
result.stdout.fnmatch_lines(['*::test_ok PASSED*'])
131+
# make sure that that we get a '0' exit code for the test suite
132+
result.assert_outcomes(passed=1)
133+
134+
assert 1, 0 == get_nb_metrics_with_cpu_freq(testdir)
135+

0 commit comments

Comments
 (0)