Skip to content

Commit

Permalink
Feature/pm #5 (#7)
Browse files Browse the repository at this point in the history
PM #5 - Modified table TEST_METRICS in order to extend ITEM's information.
              We now separate the item function and the variant used. Also, we store the 
              item's path (as a python import path) and the file path (relative to the pytest invocation 
              directory). Docs have been updated to explain the new item semantics
  • Loading branch information
js-dieu committed Mar 27, 2020
1 parent deaefba commit f14e6a1
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 25 deletions.
10 changes: 8 additions & 2 deletions docs/sources/exploitation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,14 @@ SCM_ID (TEXT 128 CHAR)
ITEM_START_TIME (TEXT 64 CHAR)
Time at which the item test was launched. The full format is
'YYYY-MM-DDTHH:MM:SS.uuuuuu' (ISO 8601 format with UTC time). The fractional second part is omitted if it is zero.
ITEM (TEXT 4096 CHAR)
Full item name.
ITEM_PATH (TEXT 4096 CHAR)
Path of the item, using an import compatible string specification.
ITEM (TEXT 2096 CHAR)
Initial item name, without any variant.
ITEM_VARIANT varchar(2048)
Full item name, with parametrization used if any.
ITEM_FS_LOC varchar(2048)
Item's module path relative to pytest invocation directory.
KIND (TEXT 64 CHAR)
Type of item (function, class, module…).
COMPONENT (TEXT 512 CHAR), NULLABLE
Expand Down
21 changes: 13 additions & 8 deletions pytest_monitor/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ def query(self, what, bind_to, many=False):
cursor.execute(what, bind_to)
return cursor.fetchall() if many else cursor.fetchone()

def insert_metric(self, run_date, item_start_date, env_id, scm_id, item, kind, component,
total_time, user_time, kernel_time, cpu_usage, mem_usage):
def insert_metric(self, run_date, item_start_date, env_id, scm_id, item, item_path, item_variant,
item_loc, kind, component, total_time, user_time, kernel_time, cpu_usage, mem_usage):
with self.__cnx:
self.__cnx.execute(f'insert into TEST_METRICS(RUN_DATE,ITEM_START_TIME,ENV_H,SCM_ID,ITEM,KIND,'
f'COMPONENT,TOTAL_TIME,USER_TIME,KERNEL_TIME,CPU_USAGE,MEM_USAGE) '
f'values (?,?,?,?,?,?,?,?,?,?,?,?)',
(run_date, item_start_date, env_id, scm_id, item, kind, component,
total_time, user_time, kernel_time, cpu_usage, mem_usage))
self.__cnx.execute(f'insert into TEST_METRICS(RUN_DATE,ITEM_START_TIME,ENV_H,SCM_ID,ITEM,'
f'ITEM_PATH,ITEM_VARIANT,ITEM_FS_LOC,KIND,COMPONENT,TOTAL_TIME,'
f'USER_TIME,KERNEL_TIME,CPU_USAGE,MEM_USAGE) '
f'values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
(run_date, item_start_date, env_id, scm_id, item, item_path,
item_variant, item_loc, kind, component, total_time, user_time,
kernel_time, cpu_usage, mem_usage))

def insert_execution_context(self, exc_context):
with self.__cnx:
Expand All @@ -38,7 +40,10 @@ def prepare(self):
ENV_H varchar(64), -- Environment description identifier
SCM_ID varchar(128),
ITEM_START_TIME varchar(64), -- Effective start time of the test
ITEM varchar(4096), -- Name of the item
ITEM_PATH varchar(4096), -- Path of the item, following Python import specification
ITEM varchar(2048), -- Name of the item
ITEM_VARIANT varchar(2048), -- Optional parametrization of an item.
ITEM_FS_LOC varchar(2048), -- Relative path from pytest invocation directory to the item's module.
KIND varchar(64), -- Package, Module or function
COMPONENT varchar(512) NULL, -- Tested component if any
TOTAL_TIME float, -- Total time spent running the item
Expand Down
26 changes: 15 additions & 11 deletions pytest_monitor/pytest_monitor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import os
# -*- coding: utf-8 -*-
import memory_profiler
import psutil
import pytest
import time
import warnings

from pytest_monitor.sys_utils import ExecutionContext
from pytest_monitor.session import PyTestMonitorSession

PYTEST_MONITOR_SESSION = None
# These dictionaries are used to compute members set on each items.
# KEY is the marker set on a test function
# value is a tuple:
Expand All @@ -22,6 +20,7 @@
'monitor_test': (False, 'monitor_force_test', lambda x: True, False),
'monitor_test_if': (True, 'monitor_force_test', lambda x: bool(x), False)}
PYTEST_MONITOR_DEPRECATED_MARKERS = {}
PYTEST_MONITOR_ITEM_LOC_MEMBER = '_location' if tuple(pytest.__version__.split('.')) < ('5', '3') else 'location'


def pytest_addoption(parser):
Expand Down Expand Up @@ -135,7 +134,7 @@ def wrapped_function():

def prof():
m = memory_profiler.memory_usage((wrapped_function, ()), max_usage=True, retval=True)
if isinstance(m[1], BaseException): # Do we have any outcome?
if isinstance(m[1], BaseException): # Do we have any outcome?
raise m[1]
setattr(pyfuncitem, 'mem_usage', m[0])
setattr(pyfuncitem, 'monitor_results', True)
Expand All @@ -145,7 +144,7 @@ def prof():

def pytest_make_parametrize_id(config, val, argname):
if config.option.want_explicit_ids:
return f'{argname}_{val}'
return f'{argname}={val}'


@pytest.hookimpl(hookwrapper=True)
Expand All @@ -154,7 +153,6 @@ def pytest_sessionstart(session):
Instantiate a monitor session to save collected metrics.
We yield at the end to let pytest pursue the execution.
"""
global PYTEST_MONITOR_SESSION
if session.config.option.force_component and session.config.option.component_prefix:
raise pytest.UsageError('Invalid usage: --force-component and --component-prefix are incompatible options!')
if session.config.option.no_db and not session.config.option.remote and not session.config.option.mtr_none:
Expand All @@ -167,9 +165,8 @@ def pytest_sessionstart(session):
component = '{user_component}'
db = None if (session.config.option.mtr_none or session.config.option.no_db) else session.config.option.mtr_db_out
remote = None if session.config.option.mtr_none else session.config.option.remote
PYTEST_MONITOR_SESSION = PyTestMonitorSession(db=db, remote=remote, component=component)
PYTEST_MONITOR_SESSION.set_environment_info(ExecutionContext())
session.pytest_monitor = PYTEST_MONITOR_SESSION
session.pytest_monitor = PyTestMonitorSession(db=db, remote=remote, component=component)
session.pytest_monitor.set_environment_info(ExecutionContext())
yield


Expand All @@ -182,7 +179,11 @@ def prf_module_tracer(request):
t_z = time.time()
rss = request.session.pytest_monitor.process.memory_info().rss / 1024 ** 2
component = getattr(request.module, 'pytest_monitor_component', '')
request.session.pytest_monitor.add_test_info(request.module.__name__, 'module', request.config.option.mtr_scope,
item = request.node.name[:-3]
pypath = request.module.__name__[:-len(item)-1]
request.session.pytest_monitor.add_test_info(item, pypath, '',
request.node._nodeid,
'module', request.config.option.mtr_scope,
component, t_a, t_z - t_a,
ptimes_b.user - ptimes_a.user,
ptimes_b.system - ptimes_a.system,
Expand All @@ -195,8 +196,11 @@ def prf_tracer(request):
yield
ptimes_b = request.session.pytest_monitor.process.cpu_times()
if not request.node.monitor_skip_test and request.node.monitor_results:
full_item_name = f'{request.module.__name__}/{request.node.name}'
request.session.pytest_monitor.add_test_info(full_item_name, 'function', request.config.option.mtr_scope,
item_name = request.node.originalname or request.node.name
item_loc = getattr(request.node, PYTEST_MONITOR_ITEM_LOC_MEMBER)[0]
request.session.pytest_monitor.add_test_info(item_name, request.module.__name__,
request.node.name, item_loc,
'function', request.config.option.mtr_scope,
request.node.monitor_component,
request.node.test_effective_start_time,
request.node.test_run_duration,
Expand Down
10 changes: 6 additions & 4 deletions pytest_monitor/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def dummy():
if self.__db:
self.__db.prepare()

def add_test_info(self, item, kind, allowed_scope, component, item_start_time, total_time,
user_time, kernel_time, mem_usage):
def add_test_info(self, item, item_path, item_variant, item_loc, kind, allowed_scope, component,
item_start_time, total_time, user_time, kernel_time, mem_usage):
if kind not in allowed_scope:
return
mem_usage = float(mem_usage) - self.__mem_usage_base
Expand All @@ -93,9 +93,11 @@ def add_test_info(self, item, kind, allowed_scope, component, item_start_time, t
final_component = self.__component.format(user_component=component)
if final_component.endswith('.'):
final_component = final_component[:-1]
item_variant = item_variant.replace('-', ', ') # No choice
if self.__db and self.db_env_id is not None:
self.__db.insert_metric(self.__run_date, item_start_time, self.db_env_id, self.__scm, item, kind,
final_component, total_time, user_time, kernel_time, cpu_usage, mem_usage)
self.__db.insert_metric(self.__run_date, item_start_time, self.db_env_id, self.__scm, item,
item_path, item_variant, item_loc, kind, final_component, total_time, user_time,
kernel_time, cpu_usage, mem_usage)
if self.__remote and self.remote_env_id is not None:
r = requests.post(f'{self.__remote}/metrics/',
json=dict(run_date=self.__run_date, context_h=self.remote_env_id, scm_ref=self.__scm,
Expand Down

0 comments on commit f14e6a1

Please sign in to comment.