Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/60 #61

Merged
merged 2 commits into from
Oct 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/sources/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Changelog
=========

* :release:`1.6.5 <2022-10-16>`
* :bug:`#60` Make sure that when psutil cannot fetch cpu frequency, the fallback mechanism is used.

* :release:`1.6.4 <2022-05-18>`
* :bug:`#56` Force the CPU frequency to 0 and emit a warning when unable to fetch it from the system.
* :bug:`#54` Fix a bug that crashes the monitor upon non ASCII characters in commit log under Perforce. Improved P4 change number extraction.
Expand Down
16 changes: 16 additions & 0 deletions docs/sources/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,19 @@ garbage collector, you just have to set the option `--no-gc` on the command line

bash $> pytest --no-gc

Forcing CPU frequency
---------------------
Under some circumstances, you may want to set the CPU frequency instead of asking `pytest-monitor` to compute it.
To do so, you can either:
- ask `pytest-monitor` to use a preset value if it does not manage to compute the CPU frequency
- or to not try computing the CPU frequency and use your preset value.

Two environment variables controls this behaviour:
- `PYTEST_MONITOR_CPU_FREQ` allows you to preset a value for the CPU frequency. It must be a float convertible value.
This value will be used if `pytest-monitor` cannot compute the CPU frequency. Otherwise, `0.0` will be used as a
default value.
- `PYTEST_MONITOR_FORCE_CPU_FREQ` instructs `pytest-monitor` to try computing the CPU frequency or not. It expects an
integer convertible value. If not set, or if the integer representation of the value is `0`, then `pytest-monitor` will
try to compute the cpu frequency and defaults to the usecase describe for the previous environment variable.
If it set and not equal to `0`, then we use the value that the environment variable `PYTEST_MONITOR_CPU_FREQ` holds
(`0.0` if not set).
7 changes: 1 addition & 6 deletions docs/sources/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,8 @@ Supported environments

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

The following versions of Python are supported:
We support all versions of Python >= 3.6.

- Python 3.5
- Python 3.6
- Python 3.7

Support for Python 3.8 is still experimental.

From conda
----------
Expand Down
2 changes: 1 addition & 1 deletion pytest_monitor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "1.6.4"
__version__ = "1.6.5"
__author__ = "Jean-Sebastien Dieu"
20 changes: 15 additions & 5 deletions pytest_monitor/sys_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@ class ExecutionContext:
def __init__(self):
self.__cpu_count = multiprocessing.cpu_count()
self.__cpu_vendor = _get_cpu_string()
try:
self.__cpu_freq_base = psutil.cpu_freq().current
except AttributeError:
warnings.warn("Unable to fetch CPU frequency. Forcing it to 0.")
self.__cpu_freq_base = 0
if int(os.environ.get('PYTEST_MONITOR_FORCE_CPU_FREQ', '0')):
self._read_cpu_freq_from_env()
else:
try:
self.__cpu_freq_base = psutil.cpu_freq().current
except (AttributeError, NotImplementedError, FileNotFoundError):
warnings.warn("Unable to fetch CPU frequency. Trying to read it from environment..")
self._read_cpu_freq_from_env()
self.__proc_typ = platform.processor()
self.__tot_mem = int(psutil.virtual_memory().total / 1024**2)
self.__fqdn = socket.getfqdn()
Expand All @@ -81,6 +84,13 @@ def __init__(self):
self.__system = '{} - {}'.format(platform.system(), platform.release())
self.__py_ver = sys.version

def _read_cpu_freq_from_env(self):
try:
self.__cpu_freq_base = float(os.environ.get('PYTEST_MONITOR_CPU_FREQ', '0.'))
except (ValueError, TypeError):
warnings.warn("Wrong type/value while reading cpu frequency from environment. Forcing to 0.0.")
self.__cpu_freq_base = 0.0

def to_dict(self):
return dict(cpu_count=self.cpu_count,
cpu_frequency=self.cpu_frequency,
Expand Down
139 changes: 116 additions & 23 deletions tests/test_monitor_context.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,135 @@
import mock
import os
import pathlib
import pytest
import sqlite3


@mock.patch('pytest_monitor.sys_utils.psutil.cpu_freq', return_value=None)
def test_when_cpu_freq_cannot_fetch_frequency(cpu_freq_mock, testdir):
"""Make sure that pytest-monitor does the job when we have issue in collecing context resources"""
CPU_FREQ_PATH = 'pytest_monitor.sys_utils.psutil.cpu_freq'

TEST_CONTENT = """
import time


def test_ok():
time.sleep(0.5)
x = ['a' * i for i in range(100)]
assert len(x) == 100
"""

def get_nb_metrics_with_cpu_freq(path):
pymon_path = pathlib.Path(str(path)) / '.pymon'
db = sqlite3.connect(path.as_posix())
cursor = db.cursor()
cursor.execute('SELECT ITEM FROM TEST_METRICS;')
nb_metrics = len(cursor.fetchall())
cursor = db.cursor()
cursor.execute('SELECT CPU_FREQUENCY_MHZ FROM EXECUTION_CONTEXTS;')
rows = cursor.fetchall()
assert 1 == len(rows)
cpu_freq = rows[0][0]
return nb_metrics, cpu_freq


def test_force_cpu_freq_set_0_use_psutil(testdir):
"""Test that when force mode is set, we do not call psutil to fetch CPU's frequency"""

# create a temporary pytest test module
testdir.makepyfile("""
import time
testdir.makepyfile(TEST_CONTENT)

with mock.patch(CPU_FREQ_PATH, return_value=1500) as cpu_freq_mock:
os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ'] = '0'
os.environ['PYTEST_MONITOR_CPU_FREQ'] = '3000'
# run pytest with the following cmd args
result = testdir.runpytest('-vv')
del os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ']
del os.environ['PYTEST_MONITOR_CPU_FREQ']
cpu_freq_mock.assert_called()

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

""")
assert 1, 3000 == get_nb_metrics_with_cpu_freq(testdir)

# run pytest with the following cmd args
result = testdir.runpytest('-vv')

def test_force_cpu_freq(testdir):
"""Test that when force mode is set, we do not call psutil to fetch CPU's frequency"""

# create a temporary pytest test module
testdir.makepyfile(TEST_CONTENT)

with mock.patch(CPU_FREQ_PATH, return_value=1500) as cpu_freq_mock:
os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ'] = '1'
os.environ['PYTEST_MONITOR_CPU_FREQ'] = '3000'
# run pytest with the following cmd args
result = testdir.runpytest('-vv')
del os.environ['PYTEST_MONITOR_FORCE_CPU_FREQ']
del os.environ['PYTEST_MONITOR_CPU_FREQ']
cpu_freq_mock.assert_not_called()

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

assert 1, 3000 == get_nb_metrics_with_cpu_freq(testdir)


@pytest.mark.parametrize('effect', [AttributeError, NotImplementedError, FileNotFoundError])
def test_when_cpu_freq_cannot_fetch_frequency_set_freq_by_using_fallback(effect, testdir):
"""Make sure that pytest-monitor fallback takes value of CPU FREQ from special env var"""
# create a temporary pytest test module
testdir.makepyfile(TEST_CONTENT)

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

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

db = sqlite3.connect(str(pymon_path))
cursor = db.cursor()
cursor.execute('SELECT ITEM FROM TEST_METRICS;')
assert 1 == len(cursor.fetchall()) # current test
cursor = db.cursor()
cursor.execute('SELECT CPU_FREQUENCY_MHZ FROM EXECUTION_CONTEXTS;')
rows = cursor.fetchall()
assert 1 == len(rows)
assert rows[0][0] == 0
assert 1, 3000 == get_nb_metrics_with_cpu_freq(testdir)


@pytest.mark.parametrize('effect', [AttributeError, NotImplementedError, FileNotFoundError])
def test_when_cpu_freq_cannot_fetch_frequency_set_freq_to_0(effect, testdir):
"""Make sure that pytest-monitor's fallback mechanism is efficient enough. """
# create a temporary pytest test module
testdir.makepyfile(TEST_CONTENT)

with mock.patch(CPU_FREQ_PATH, side_effect=effect) as cpu_freq_mock:
# run pytest with the following cmd args
result = testdir.runpytest('-vv')
cpu_freq_mock.assert_called()

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

assert 1, 0 == get_nb_metrics_with_cpu_freq(testdir)


@mock.patch('pytest_monitor.sys_utils.psutil.cpu_freq', return_value=None)
def test_when_cpu_freq_cannot_fetch_frequency(cpu_freq_mock, testdir):
"""Make sure that pytest-monitor does the job when we have issue in collecing context resources"""
# create a temporary pytest test module
testdir.makepyfile(TEST_CONTENT)

# run pytest with the following cmd args
result = testdir.runpytest('-vv')

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

assert 1, 0 == get_nb_metrics_with_cpu_freq(testdir)