From c1f770f6c2f6647bcb24cb962feb79bb2ccd8aed Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 6 Feb 2024 11:49:49 +0100 Subject: [PATCH 01/27] add code to benchmark --- backend/geonature/tests/test_benchmark.py | 68 +++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 backend/geonature/tests/test_benchmark.py diff --git a/backend/geonature/tests/test_benchmark.py b/backend/geonature/tests/test_benchmark.py new file mode 100644 index 0000000000..f41edafa3d --- /dev/null +++ b/backend/geonature/tests/test_benchmark.py @@ -0,0 +1,68 @@ +from .utils import set_logged_user +from .fixtures import users +from .test_pr_occhab import stations +from flask import url_for +import pytest + + +class GetLatter: + def __init__(self, value) -> None: + self.value = value + + +def generate_func_test(func, *args, **kwargs): + fixture = kwargs.pop("fixture", []) + user_profile = kwargs.pop("user_profile", None) + + if user_profile: + fixture = fixture + [users] + + def _f_to_include_fixture(self, *fixture): + + def test_function(self, benchmark, users): + + if user_profile: + if not user_profile in users: + raise KeyError(f"{user_profile} can't be found in the users fixture !") + set_logged_user(self.client, users[user_profile]) + benchmark( + eval(func.value) if isinstance(func, GetLatter) else func, + *[eval(arg.value) if isinstance(arg, GetLatter) else arg for arg in args], + **kwargs, + ) + + return test_function + + return _f_to_include_fixture(*fixture) + + +tests = [ + [ + GetLatter("self.client.get"), + "get_station", + [GetLatter("""url_for("occhab.get_station", id_station=8)""")], + dict(user_profile="user", fixture=[stations]), + ], + [ + GetLatter("self.client.get"), + "get_default_nomenclatures", + [GetLatter("""url_for("gn_synthese.getDefaultsNomenclatures")""")], + dict(user_profile="self_user"), + ], +] + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestBenchie: + pass + + +for func, name, args, kwargs in tests: + setattr(TestBenchie, f"test_{name}", generate_func_test(func, *args, **kwargs)) + +# print(dir(TestBenchie)) + + +# def test_routes(app): +# for rule in app.url_map.iter_rules(): +# print(rule.endpoint) From a09f77283638604d99b39dc331d90b6abc690d05 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 6 Feb 2024 14:27:19 +0100 Subject: [PATCH 02/27] create an object TestBenchmark + add docstring --- backend/geonature/tests/test_benchmark.py | 106 ++++++++++++++++------ 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/backend/geonature/tests/test_benchmark.py b/backend/geonature/tests/test_benchmark.py index f41edafa3d..3e6b42909e 100644 --- a/backend/geonature/tests/test_benchmark.py +++ b/backend/geonature/tests/test_benchmark.py @@ -1,3 +1,4 @@ +from typing import Any, Dict, Sequence, Callable from .utils import set_logged_user from .fixtures import users from .test_pr_occhab import stations @@ -10,45 +11,100 @@ def __init__(self, value) -> None: self.value = value -def generate_func_test(func, *args, **kwargs): - fixture = kwargs.pop("fixture", []) - user_profile = kwargs.pop("user_profile", None) +class BenchmarkTest: + """ + Class that allows to define a benchmark test and generate the pytest function to run the benchmark. - if user_profile: - fixture = fixture + [users] + Example, in a pytest file: + ```python + import pytest + bench = BenchmarkTest(print,"test_print",["Hello","World"],{}) + @pytest.mark.usefixtures("client_class", "temporary_transaction") + class TestBenchie: + pass + TestBenchie.test_print = bench.generate_func_test() + ``` - def _f_to_include_fixture(self, *fixture): + If a function or its argument depend on the pytest function context, use the GetLatter class : GetLatter("). For example, to use + the `url_for()` function, replace from `url_for(...)` to `GetLatter("url_for(...)")`. - def test_function(self, benchmark, users): + If the benchmark requires a user to be logged, use the `function_kwargs` with the "user_profile" key and the value corresponds to a key + available in the dictionary returned by the `user` fixture. - if user_profile: - if not user_profile in users: - raise KeyError(f"{user_profile} can't be found in the users fixture !") - set_logged_user(self.client, users[user_profile]) - benchmark( - eval(func.value) if isinstance(func, GetLatter) else func, - *[eval(arg.value) if isinstance(arg, GetLatter) else arg for arg in args], - **kwargs, - ) - return test_function + """ - return _f_to_include_fixture(*fixture) + def __init__(self, function, name_benchmark, function_args=[], function_kwargs={}) -> None: + """ + Constructor of BenchmarkTest + + Parameters + ---------- + function : Callable | GetLatter + function that will be benchmark + name_benchmark : str + name of the benchmark + function_args : Sequence[Any | GetLatter] + args for the function + function_kwargs : Dict[str,Any] + kwargs for the function + """ + self.function = function + self.name_benchmark = name_benchmark + self.function_args = function_args + self.function_kwargs = function_kwargs + + def generate_func_test(self): + """ + Return the pytest function to run the benchmark on the indicated function. + + Returns + ------- + Callable + test function + + Raises + ------ + KeyError + if the user_profile given do not exists + """ + fixture = self.function_kwargs.pop("fixture", []) + user_profile = self.function_kwargs.pop("user_profile", None) + + func, args, kwargs = self.function, self.function_args, self.function_kwargs + + def function_to_include_fixture(*fixture): + + def final_test_function(self, benchmark, users): + + if user_profile: + if not user_profile in users: + raise KeyError(f"{user_profile} can't be found in the users fixture !") + set_logged_user(self.client, users[user_profile]) + benchmark( + eval(func.value) if isinstance(func, GetLatter) else func, + *[eval(arg.value) if isinstance(arg, GetLatter) else arg for arg in args], + **kwargs, + ) + + return final_test_function + + return function_to_include_fixture(*fixture) tests = [ - [ + BenchmarkTest( GetLatter("self.client.get"), "get_station", [GetLatter("""url_for("occhab.get_station", id_station=8)""")], dict(user_profile="user", fixture=[stations]), - ], - [ + ), + BenchmarkTest( GetLatter("self.client.get"), "get_default_nomenclatures", [GetLatter("""url_for("gn_synthese.getDefaultsNomenclatures")""")], dict(user_profile="self_user"), - ], + ), ] @@ -57,10 +113,8 @@ class TestBenchie: pass -for func, name, args, kwargs in tests: - setattr(TestBenchie, f"test_{name}", generate_func_test(func, *args, **kwargs)) - -# print(dir(TestBenchie)) +for test in tests: + setattr(TestBenchie, f"test_{test.name_benchmark}", test.generate_func_test()) # def test_routes(app): From a82c66d1ef4b0d7f0eec8d8a1c4cac929fc2df26 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 6 Feb 2024 17:39:02 +0100 Subject: [PATCH 03/27] add ci for eval perf + add requirements to setup.py --- .github/workflows/pytestPerf.yml | 126 +++++++++++++++++++++++++++++++ setup.py | 9 ++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pytestPerf.yml diff --git a/.github/workflows/pytestPerf.yml b/.github/workflows/pytestPerf.yml new file mode 100644 index 0000000000..43ac15a4b6 --- /dev/null +++ b/.github/workflows/pytestPerf.yml @@ -0,0 +1,126 @@ +name: pytestPerf + +on: + push: + branches: + - master + - hotfixes + - develop + pull_request: + branches: + - master + - hotfixes + - develop + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + debian-version: ["11", "12"] + include: + # - debian-version: "11" + # python-version: "3.9" + # postgres-version: "13" + # postgis-version: "3.2" + - debian-version: "12" + python-version: "3.11" + postgres-version: "15" + postgis-version: "3.3" + + name: Debian ${{ matrix.debian-version }} + + services: + postgres: + image: postgis/postgis:${{ matrix.postgres-version }}-${{ matrix.postgis-version }} + env: + POSTGRES_DB: geonature2db + POSTGRES_PASSWORD: geonatpasswd + POSTGRES_USER: geonatadmin + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Add database extensions + run: | + psql -h localhost -U geonatadmin -d geonature2db -f install/assets/db/add_pg_extensions.sql + env: + PGPASSWORD: geonatpasswd + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + - name: Install GDAL + run: | + sudo apt update + sudo apt install -y libgdal-dev + - name: Install dependencies + if: github.base_ref == 'master' + run: | + echo 'Installation des requirements de prod' + python -m pip install --upgrade pip + python -m pip install \ + -e ..[tests] \ + -r requirements.txt + working-directory: ./backend + - name: Install dependencies + if: github.base_ref != 'master' + run: | + echo 'Installation des requirements de dev' + python -m pip install --upgrade pip + python -m pip install \ + -e ..[tests] \ + -r requirements-dev.in + working-directory: ./backend + - name: Show database branches and dependencies + run: | + geonature db status --dependencies + env: + GEONATURE_CONFIG_FILE: config/test_config.toml + - name: Install database + run: | + wget https://geonature.fr/data/2024-02-GeoNature-Backup-Synthese-9000 + pg_restore -U geonatadmin -d geonature2db -1 2024-02-GeoNature-Backup-Synthese-9000 + env: + PGPASSWORD: geonatpasswd + - name: Show database status + run: | + geonature db status + env: + GEONATURE_CONFIG_FILE: config/test_config.toml + - name: Install core modules backend + run: | + pip install -e contrib/occtax + pip install -e contrib/gn_module_occhab + pip install -e contrib/gn_module_validation + - name: Install core modules database + run: | + geonature upgrade-modules-db + env: + GEONATURE_CONFIG_FILE: config/test_config.toml + - name: Show database status + run: | + geonature db status --dependencies + env: + GEONATURE_CONFIG_FILE: config/test_config.toml + - name: Test with pytest + run: | + pytest -v --cov --cov-report xml + env: + GEONATURE_CONFIG_FILE: config/test_config.toml + - name: Upload coverage to Codecov + if: ${{ matrix.debian-version == '11' }} + uses: codecov/codecov-action@v3 + with: + flags: pytest diff --git a/setup.py b/setup.py index a87c593671..6058beb1c9 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,14 @@ install_requires=list(open("backend/requirements-common.in", "r")) + list(open("backend/requirements-dependencies.in", "r")), extras_require={ - "tests": ["pytest", "pytest-flask", "pytest-cov", "jsonschema", "pandas"], + "tests": [ + "pytest", + "pytest-flask", + "pytest-benchmark", + "pytest-cov", + "jsonschema", + "pandas", + ], "doc": [ "sphinx", "sphinx_rtd_theme", From 4d2e163c377343ed89fdaf777563de8c77aba832 Mon Sep 17 00:00:00 2001 From: VincentCauchois Date: Tue, 6 Feb 2024 18:14:47 +0100 Subject: [PATCH 04/27] feat(benchmark): add sqlalchemy query profiling Add a fixture to activate profiling of each SQL query executed and store the results into a csv. --- backend/geonature/tests/test_benchmark.py | 55 ++++++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/backend/geonature/tests/test_benchmark.py b/backend/geonature/tests/test_benchmark.py index 3e6b42909e..4ebdeb1d4c 100644 --- a/backend/geonature/tests/test_benchmark.py +++ b/backend/geonature/tests/test_benchmark.py @@ -92,13 +92,54 @@ def final_test_function(self, benchmark, users): return function_to_include_fixture(*fixture) +from geonature.utils.env import db +from sqlalchemy import event +from sqlalchemy.engine import Engine +import time +import logging + +logging.basicConfig() +logger = logging.getLogger("logger-name") +logger.setLevel(logging.DEBUG) + +import pandas + + +@pytest.fixture(scope="class") +def activate_profiling_sql(): + """ + Fixture to activate profiling for SQL queries and storing query's statements and execution times in a csv file. + """ + + results_file = "sql_queries.csv" + df = pandas.DataFrame([], columns=["Query", "Total Time [s.]"]) + df.to_csv(results_file, mode="a", header=True, index=None, sep=";") + + # @event.listens_for(Engine, "before_cursor_execute") + def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): + conn.info.setdefault("query_start_time", []).append(time.time()) + logger.debug("Start Query: %s" % statement) + + # @event.listens_for(Engine, "after_cursor_execute") + def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): + total = time.time() - conn.info["query_start_time"].pop(-1) + logger.debug("Query Complete!") + logger.debug("Total Time: %f" % total) + if statement.startswith("SELECT"): + df = pandas.DataFrame([[statement, total]], columns=["Query", "Total Time"]) + df.to_csv(results_file, mode="a", header=False, index=None, sep=";") + + event.listen(db.engine, "before_cursor_execute", before_cursor_execute) + event.listen(db.engine, "after_cursor_execute", after_cursor_execute) + + tests = [ - BenchmarkTest( - GetLatter("self.client.get"), - "get_station", - [GetLatter("""url_for("occhab.get_station", id_station=8)""")], - dict(user_profile="user", fixture=[stations]), - ), + # BenchmarkTest( + # GetLatter("self.client.get"), + # "get_station", + # [GetLatter("""url_for("occhab.get_station", id_station=8)""")], + # dict(user_profile="user", fixture=[stations]), + # ), BenchmarkTest( GetLatter("self.client.get"), "get_default_nomenclatures", @@ -108,7 +149,7 @@ def final_test_function(self, benchmark, users): ] -@pytest.mark.usefixtures("client_class", "temporary_transaction") +@pytest.mark.usefixtures("client_class", "temporary_transaction", "activate_profiling_sql") class TestBenchie: pass From 1a8e01e00e9ccf902f4aa137fe8d9d132d7eb17c Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 6 Feb 2024 18:17:54 +0100 Subject: [PATCH 05/27] fix(ci): update versions for github workflow pytestperf --- .../{pytestPerf.yml => eval_perf.yml} | 54 +++++++++---------- setup.py | 1 + 2 files changed, 26 insertions(+), 29 deletions(-) rename .github/workflows/{pytestPerf.yml => eval_perf.yml} (74%) diff --git a/.github/workflows/pytestPerf.yml b/.github/workflows/eval_perf.yml similarity index 74% rename from .github/workflows/pytestPerf.yml rename to .github/workflows/eval_perf.yml index 43ac15a4b6..a0680b35e9 100644 --- a/.github/workflows/pytestPerf.yml +++ b/.github/workflows/eval_perf.yml @@ -1,30 +1,21 @@ -name: pytestPerf +name: Routes performance evaluation on: - push: - branches: - - master - - hotfixes - - develop - pull_request: - branches: - - master - - hotfixes - - develop + workflow_dispatch: jobs: build: - runs-on: ubuntu-latest + runs-on: self-hosted strategy: fail-fast: false matrix: debian-version: ["11", "12"] include: - # - debian-version: "11" - # python-version: "3.9" - # postgres-version: "13" - # postgis-version: "3.2" + - debian-version: "11" + python-version: "3.9" + postgres-version: "13" + postgis-version: "3.2" - debian-version: "12" python-version: "3.11" postgres-version: "15" @@ -88,22 +79,31 @@ jobs: geonature db status --dependencies env: GEONATURE_CONFIG_FILE: config/test_config.toml - - name: Install database + - name: Restore database run: | - wget https://geonature.fr/data/2024-02-GeoNature-Backup-Synthese-9000 - pg_restore -U geonatadmin -d geonature2db -1 2024-02-GeoNature-Backup-Synthese-9000 + # wget https://www.dropbox.com/scl/fi/17gsthsftfg59mxwmbbre/export_geonature_10000.zip?rlkey=33choleag4xw60wadm802c3oh&dl=1 -O 10kDump.zip + # unzip 10kDump.zip + wget https://www.dropbox.com/scl/fi/jjkxyg120bxc0dp8uy8kq/300KDump.sql?rlkey=tyuk2svitcb9nyshn7r09yo7b&dl=1 -O 300KDump.sql + ls + psql -h localhost -U geonatadmin -d geonature2db -f 300KDump.sql env: PGPASSWORD: geonatpasswd - - name: Show database status + - name: Install module import run: | - geonature db status - env: - GEONATURE_CONFIG_FILE: config/test_config.toml + wget https://github.com/PnX-SI/gn_module_import/archive/refs/heads/develop.zip + unzip develop.zip + cd gn_module_import-develop + pip install -e . - name: Install core modules backend run: | pip install -e contrib/occtax pip install -e contrib/gn_module_occhab pip install -e contrib/gn_module_validation + - name: Show database status + run: | + geonature db status + env: + GEONATURE_CONFIG_FILE: config/test_config.toml - name: Install core modules database run: | geonature upgrade-modules-db @@ -116,11 +116,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Test with pytest run: | - pytest -v --cov --cov-report xml + pytest backend/geonature/tests/test_benchmark.py env: GEONATURE_CONFIG_FILE: config/test_config.toml - - name: Upload coverage to Codecov - if: ${{ matrix.debian-version == '11' }} - uses: codecov/codecov-action@v3 - with: - flags: pytest + # https://stackoverflow.com/a/64126737 For posting results on GitHub Pull Requests diff --git a/setup.py b/setup.py index 6058beb1c9..9695ffdc23 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ + list(open("backend/requirements-dependencies.in", "r")), extras_require={ "tests": [ + "pandas", "pytest", "pytest-flask", "pytest-benchmark", From f1d39ef6a645743992d6b82c9720af37c1485801 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Mon, 19 Feb 2024 13:28:02 +0100 Subject: [PATCH 06/27] add new benchmark test --- .github/workflows/eval_perf.yml | 2 +- backend/geonature/tests/benchmark_data.py | 561 ++++++++++++++++++++++ backend/geonature/tests/test_benchmark.py | 48 +- 3 files changed, 604 insertions(+), 7 deletions(-) create mode 100644 backend/geonature/tests/benchmark_data.py diff --git a/.github/workflows/eval_perf.yml b/.github/workflows/eval_perf.yml index a0680b35e9..f9f5134659 100644 --- a/.github/workflows/eval_perf.yml +++ b/.github/workflows/eval_perf.yml @@ -5,7 +5,7 @@ on: jobs: build: - runs-on: self-hosted + runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/backend/geonature/tests/benchmark_data.py b/backend/geonature/tests/benchmark_data.py new file mode 100644 index 0000000000..d8327007ab --- /dev/null +++ b/backend/geonature/tests/benchmark_data.py @@ -0,0 +1,561 @@ +benchmark_synthese_intersection_data_test_bbox = { + "modif_since_validation": False, + "geoIntersection": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [6.363466, 44.754607], + [6.363466, 44.892886], + [6.519971, 44.892886], + [6.519971, 44.754607], + [6.363466, 44.754607], + ] + ], + }, + }, +} + +benchmark_synthese_intersection_data_test_complex_polygon = { + "modif_since_validation": False, + "geoIntersection": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [6.373232, 44.894802], + [6.363622, 44.871455], + [6.352639, 44.846151], + [6.356758, 44.843231], + [6.381469, 44.838363], + [6.381572, 44.833973], + [6.376767, 44.817419], + [6.367158, 44.814984], + [6.353429, 44.814497], + [6.352743, 44.800373], + [6.365098, 44.792579], + [6.374022, 44.794527], + [6.377454, 44.783322], + [6.369903, 44.772114], + [6.334895, 44.773089], + [6.321167, 44.774063], + [6.311557, 44.764316], + [6.345192, 44.75603], + [6.359607, 44.746279], + [6.356175, 44.739453], + [6.37814, 44.729213], + [6.41864, 44.755055], + [6.438546, 44.753105], + [6.44541, 44.741404], + [6.463944, 44.744329], + [6.419326, 44.723848], + [6.439919, 44.714092], + [6.504443, 44.726774], + [6.549061, 44.755542], + [6.573772, 44.783809], + [6.540137, 44.816932], + [6.55112, 44.825209], + [6.542883, 44.847115], + [6.569654, 44.861228], + [6.547001, 44.885552], + [6.549061, 44.89771], + [6.510621, 44.922019], + [6.500324, 44.909379], + [6.494833, 44.884093], + [6.470122, 44.882633], + [6.461198, 44.904517], + [6.480418, 44.931253], + [6.476986, 44.946803], + [6.400106, 44.940486], + [6.41864, 44.919588], + [6.42619, 44.915213], + [6.411089, 44.890902], + [6.38775, 44.922991], + [6.340387, 44.929309], + [6.306752, 44.91181], + [6.286846, 44.896737], + [6.304006, 44.887497], + [6.343819, 44.885552], + [6.36098, 44.892847], + [6.373232, 44.894802], + ] + ], + }, + }, +} + +benchmark_synthese_intersection_data_test_commune = { + "modif_since_validation": False, + "area_COM": [28612], +} + +benchmark_synthese_with_tree_taxon = { + "modif_since_validation": False, + "cd_ref_parent": [ + 183787, + 183767, + 183839, + 183840, + 183843, + 183874, + 184358, + 184357, + 184355, + 184354, + 184353, + 184367, + 184376, + 927956, + 200726, + 184350, + 184351, + 184366, + 184363, + 184362, + 184360, + 184359, + 184377, + 184379, + 184374, + 184371, + 184369, + 184368, + 184381, + 184393, + 184391, + 184390, + 184385, + 184387, + 184388, + 184378, + 184438, + 184449, + 184450, + 184493, + 1007942, + 184501, + 184486, + 184488, + 184491, + 184499, + 184492, + 184494, + 200765, + 184737, + 184740, + 184743, + 184752, + 184756, + 184762, + 184766, + 184767, + 184768, + 184771, + 184773, + 184775, + 184776, + 184772, + 184615, + 184616, + 184618, + 184630, + 184643, + 188023, + 200773, + 793340, + 184644, + 184646, + 184649, + 184650, + 184653, + 184658, + 184660, + 184662, + 184666, + 184667, + 184668, + 184670, + 184671, + 184672, + 184674, + 184676, + 184680, + 184682, + 184684, + 184685, + 184689, + 184694, + 184696, + 184699, + 184701, + 184702, + 184703, + 184706, + 184708, + 184709, + 184711, + 184712, + 184714, + 184715, + 184718, + 184720, + 184728, + 184732, + 184734, + 184735, + 184781, + 184791, + 184802, + 184801, + 184890, + 184808, + 184810, + 184815, + 184831, + 184836, + 184838, + 184839, + 184843, + 184847, + 184848, + 184852, + 184853, + 184855, + 184862, + 184865, + 184867, + 184870, + 184872, + 184874, + 184877, + 184879, + 184880, + 184882, + 184883, + 184885, + 184888, + 184889, + 184891, + 184892, + 184894, + 184896, + 184900, + 184907, + 184927, + 184944, + 184947, + 184948, + 184949, + 184950, + 184952, + 839211, + 184961, + 184976, + 185027, + 185028, + 185030, + 185033, + 185043, + 185046, + 185051, + 185064, + 185075, + 185076, + 185077, + 185078, + 185023, + 185081, + 185082, + 185084, + 185085, + 185088, + 185090, + 185124, + 185080, + 185025, + 645275, + 184994, + 834425, + 839203, + 938486, + 184992, + 184988, + 184984, + 184983, + 938483, + 938487, + 185012, + 185204, + 185211, + 188009, + 827922, + 728177, + 187977, + 827924, + 827925, + 714463, + 185129, + 939246, + 185130, + 185131, + 185133, + 185150, + 185157, + 185162, + 185171, + 185188, + 185189, + 185190, + 185193, + 185194, + 185197, + 714275, + 648561, + 610362, + 602182, + 200681, + 185216, + 185220, + 185223, + 185224, + 185226, + 185230, + 185232, + 185234, + 185240, + 185242, + 185244, + 185245, + 185247, + 185249, + 185251, + 185253, + 185254, + 185257, + 185259, + 185263, + 185266, + 185268, + 185270, + 185273, + 185274, + 185276, + 185278, + 185280, + 185285, + 185286, + 185287, + 185290, + 185291, + 185292, + 185297, + 185302, + 185307, + 185308, + 185309, + 185313, + 185322, + 185323, + 185324, + 185327, + 185330, + 714314, + 185334, + 185352, + 185346, + 185347, + 185351, + 185369, + 185372, + 185371, + 185365, + 185364, + 185362, + 185360, + 185359, + 185374, + 185378, + 185388, + 185389, + 185399, + 437022, + 185383, + 185385, + 185468, + 185467, + 185471, + 185472, + 185473, + 185517, + 185566, + 185554, + 185568, + 185557, + 185582, + 185580, + 185578, + 528744, + 185401, + 185590, + 185593, + 185599, + 185596, + 185594, + 185602, + 185604, + 185621, + 185635, + 185652, + 185651, + 185649, + 185669, + 185674, + 186286, + 699191, + 618462, + 186293, + 186292, + 186283, + 185770, + 185781, + 185830, + 185832, + 185896, + 185900, + 185760, + 905695, + 904968, + 827284, + 825345, + 185967, + 185979, + 186008, + 186010, + 186012, + 186000, + 186006, + 185998, + 186058, + 186067, + 186062, + 186076, + 186091, + 186084, + 186094, + 186101, + 885360, + 885396, + 885400, + 885430, + 186121, + 186117, + 186115, + 186110, + 186135, + 186107, + 186108, + 186137, + 186139, + 186141, + 186145, + 186146, + 186148, + 186152, + 186154, + 186158, + 186160, + 186130, + 186129, + 186124, + 186122, + 828783, + 186168, + 186178, + 186176, + 186027, + 186047, + 186021, + 186019, + 186038, + 186025, + 351923, + 186173, + 185985, + 186055, + 186053, + 185990, + 186221, + 186214, + 186223, + 186215, + 186238, + 186237, + 186239, + 699095, + 186245, + 186257, + 186259, + 655502, + 199825, + 443494, + 655482, + 186210, + 186209, + 186211, + 186241, + 186243, + 186242, + 199959, + 444434, + 185954, + 185951, + 185946, + 185960, + 186330, + 933748, + 186337, + 186332, + 186374, + 186376, + 186365, + 186377, + 186346, + 186409, + 186380, + 186383, + 186386, + 186388, + 186390, + 186391, + 186392, + 186393, + 186396, + 186399, + 186400, + 186401, + 186404, + 186406, + 186407, + 186415, + 186418, + 186422, + 186423, + 186425, + 186426, + 186430, + 187541, + 851431, + 851437, + 851464, + 186356, + 810391, + 699677, + 186353, + 186369, + 186997, + ], +} diff --git a/backend/geonature/tests/test_benchmark.py b/backend/geonature/tests/test_benchmark.py index 4ebdeb1d4c..f5166c4ab4 100644 --- a/backend/geonature/tests/test_benchmark.py +++ b/backend/geonature/tests/test_benchmark.py @@ -5,6 +5,8 @@ from flask import url_for import pytest +from .benchmark_data import * + class GetLatter: def __init__(self, value) -> None: @@ -133,19 +135,53 @@ def after_cursor_execute(conn, cursor, statement, parameters, context, executema event.listen(db.engine, "after_cursor_execute", after_cursor_execute) +# r = self.client.post(url_for("gn_synthese.get_observations_for_web"), json=filters) tests = [ - # BenchmarkTest( - # GetLatter("self.client.get"), - # "get_station", - # [GetLatter("""url_for("occhab.get_station", id_station=8)""")], - # dict(user_profile="user", fixture=[stations]), - # ), + BenchmarkTest( + GetLatter("self.client.get"), + "get_station", + [GetLatter("""url_for("occhab.get_station", id_station=8)""")], + dict(user_profile="user", fixture=[stations]), + ), BenchmarkTest( GetLatter("self.client.get"), "get_default_nomenclatures", [GetLatter("""url_for("gn_synthese.getDefaultsNomenclatures")""")], dict(user_profile="self_user"), ), + BenchmarkTest( + GetLatter("self.client.post"), + "synthese_with_geometry_bbox", + [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], + dict(user_profile="admin_user", json=benchmark_synthese_intersection_data_test_bbox), + ), + BenchmarkTest( + GetLatter("self.client.post"), + "synthese_with_geometry_complex_poly", + [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=benchmark_synthese_intersection_data_test_complex_polygon, + ), + ), + BenchmarkTest( + GetLatter("self.client.post"), + "synthese_with_commune", + [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=benchmark_synthese_intersection_data_test_commune, + ), + ), + BenchmarkTest( + GetLatter("self.client.post"), + "synthese_with_up_tree_taxon", + [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=benchmark_synthese_with_tree_taxon, + ), + ), ] From af9d59ed0791933599e8fdea7bc71114c35495ae Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Thu, 22 Feb 2024 11:04:17 +0100 Subject: [PATCH 07/27] add tests for synthese + separate classes in different files + separate tests per module + kwargs can now use CLater --- .../geonature/tests/benchmarks/__init__.py | 3 + .../tests/{ => benchmarks}/benchmark_data.py | 50 ++++- .../tests/benchmarks/benchmark_generator.py | 99 +++++++++ .../tests/benchmarks/test_benchmark_occhab.py | 22 ++ .../benchmarks/test_benchmark_synthese.py | 70 ++++++ backend/geonature/tests/benchmarks/utils.py | 44 ++++ backend/geonature/tests/test_benchmark.py | 199 ------------------ 7 files changed, 284 insertions(+), 203 deletions(-) create mode 100644 backend/geonature/tests/benchmarks/__init__.py rename backend/geonature/tests/{ => benchmarks}/benchmark_data.py (89%) create mode 100644 backend/geonature/tests/benchmarks/benchmark_generator.py create mode 100644 backend/geonature/tests/benchmarks/test_benchmark_occhab.py create mode 100644 backend/geonature/tests/benchmarks/test_benchmark_synthese.py create mode 100644 backend/geonature/tests/benchmarks/utils.py delete mode 100644 backend/geonature/tests/test_benchmark.py diff --git a/backend/geonature/tests/benchmarks/__init__.py b/backend/geonature/tests/benchmarks/__init__.py new file mode 100644 index 0000000000..dba8d97bde --- /dev/null +++ b/backend/geonature/tests/benchmarks/__init__.py @@ -0,0 +1,3 @@ +# Import required for CLater class when using eval() +from flask import url_for +from .benchmark_data import * diff --git a/backend/geonature/tests/benchmark_data.py b/backend/geonature/tests/benchmarks/benchmark_data.py similarity index 89% rename from backend/geonature/tests/benchmark_data.py rename to backend/geonature/tests/benchmarks/benchmark_data.py index d8327007ab..67e3faff9e 100644 --- a/backend/geonature/tests/benchmark_data.py +++ b/backend/geonature/tests/benchmarks/benchmark_data.py @@ -1,3 +1,8 @@ +from geonature.utils.env import db +import pytest +from ref_geo.models import BibAreasTypes, LAreas +from sqlalchemy import select + benchmark_synthese_intersection_data_test_bbox = { "modif_since_validation": False, "geoIntersection": { @@ -88,10 +93,47 @@ }, } -benchmark_synthese_intersection_data_test_commune = { - "modif_since_validation": False, - "area_COM": [28612], -} + +def benchmark_synthese_intersection_data_test_commune(): + return { + "modif_since_validation": False, + "area_COM": [ + db.session.scalars( + select(LAreas).join(BibAreasTypes).where(BibAreasTypes.type_code == "COM").limit(1) + ) + .one() + .id_area + ], + } + + +def benchmark_synthese_intersection_data_test_departement(): + return { + "modif_since_validation": False, + "area_DEP": [ + db.session.scalars( + select(LAreas.id_area) + .join(BibAreasTypes) + .where(BibAreasTypes.type_code == "DEP") + .limit(1) + ).first() + ], + } + + +def benchmark_synthese_intersection_data_test_region(): + return { + "modif_since_validation": False, + "area_REG": [ + db.session.scalars( + select(LAreas.id_area) + .join(BibAreasTypes) + .where(BibAreasTypes.type_code == "REG") + .limit(1) + ).first() + ], + } + benchmark_synthese_with_tree_taxon = { "modif_since_validation": False, diff --git a/backend/geonature/tests/benchmarks/benchmark_generator.py b/backend/geonature/tests/benchmarks/benchmark_generator.py new file mode 100644 index 0000000000..edd1f6cab7 --- /dev/null +++ b/backend/geonature/tests/benchmarks/benchmark_generator.py @@ -0,0 +1,99 @@ +from typing import Any +from geonature.tests.utils import set_logged_user +from geonature.tests.fixtures import users + +import importlib +from geonature.tests.benchmarks import * + + +class CLater: + def __init__(self, value) -> None: + self.value = value + + +class BenchmarkTest: + """ + Class that allows to define a benchmark test and generate the pytest function to run the benchmark. + + Example, in a pytest file: + ```python + import pytest + bench = BenchmarkTest(print,"test_print",["Hello","World"],{}) + @pytest.mark.usefixtures("client_class", "temporary_transaction") + class TestBenchie: + pass + TestBenchie.test_print = bench.generate_func_test() + ``` + + If a function or its argument depend on the pytest function context, use the GetLatter class : GetLatter("). For example, to use + the `url_for()` function, replace from `url_for(...)` to `GetLatter("url_for(...)")`. + + If the benchmark requires a user to be logged, use the `function_kwargs` with the "user_profile" key and the value corresponds to a key + available in the dictionary returned by the `user` fixture. + + + """ + + def __init__(self, function, function_args=[], function_kwargs={}) -> None: + """ + Constructor of BenchmarkTest + + Parameters + ---------- + function : Callable | GetLatter + function that will be benchmark + name_benchmark : str + name of the benchmark + function_args : Sequence[Any | GetLatter] + args for the function + function_kwargs : Dict[str,Any] + kwargs for the function + """ + self.function = function + self.function_args = function_args + self.function_kwargs = function_kwargs + + def __call__(self, *args: Any, **kwds: Any) -> Any: + return self.generate_func_test() + + def generate_func_test(self): + """ + Return the pytest function to run the benchmark on the indicated function. + + Returns + ------- + Callable + test function + + Raises + ------ + KeyError + if the user_profile given do not exists + """ + + fixtures = self.function_kwargs.pop("fixtures", []) + user_profile = self.function_kwargs.pop("user_profile", None) + imports = self.function_kwargs.pop("imports", []) + + func, args, kwargs = self.function, self.function_args, self.function_kwargs + + def function_to_include_fixture(*fixture): + + def final_test_function(self, benchmark, users): + + if user_profile: + if not user_profile in users: + raise KeyError(f"{user_profile} can't be found in the users fixture !") + set_logged_user(self.client, users[user_profile]) + benchmark( + eval(func.value) if isinstance(func, CLater) else func, + *[eval(arg.value) if isinstance(arg, CLater) else arg for arg in args], + **{ + key: eval(value.value) if isinstance(value, CLater) else value + for key, value in kwargs.items() + }, + ) + + return final_test_function + + return function_to_include_fixture(*fixtures) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py new file mode 100644 index 0000000000..5c6fd4f605 --- /dev/null +++ b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py @@ -0,0 +1,22 @@ +import logging +import pytest +from geonature.tests.benchmarks import * +from geonature.tests.test_pr_occhab import stations + +from .benchmark_generator import BenchmarkTest, CLater + +logging.basicConfig() +logger = logging.getLogger("logger-name") +logger.setLevel(logging.DEBUG) + +from .utils import CLIENT_GET, CLIENT_POST + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestBenchmarkOcchab: + + test_get_station = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("occhab.get_station", id_station=8)""")], + dict(user_profile="user", fixtures=[stations]), + )() diff --git a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py new file mode 100644 index 0000000000..e80cb48974 --- /dev/null +++ b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py @@ -0,0 +1,70 @@ +import logging +import pytest +from geonature.tests.benchmarks import * +from geonature.tests.test_pr_occhab import stations + +from .benchmark_generator import BenchmarkTest, CLater + +logging.basicConfig() +logger = logging.getLogger("logger-name") +logger.setLevel(logging.DEBUG) + +from .utils import CLIENT_GET, CLIENT_POST + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") # , "activate_profiling_sql") +class TestBenchmarkSynthese: + test_get_default_nomenclatures = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("gn_synthese.getDefaultsNomenclatures")""")], + dict(user_profile="self_user"), + )() + test_synthese_with_geometry_bbox = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=benchmark_synthese_intersection_data_test_bbox, + ), + )() + test_synthese_with_geometry_complex_poly = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=benchmark_synthese_intersection_data_test_complex_polygon, + ), + )() + test_synthese_with_commune = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_commune()"), + ), + )() + + test_synthese_with_departement = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_departement()"), + ), + )() + test_synthese_with_region = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_region()"), + ), + )() + test_synthese_with_up_tree_taxon = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="admin_user", + json=benchmark_synthese_with_tree_taxon, + ), + )() diff --git a/backend/geonature/tests/benchmarks/utils.py b/backend/geonature/tests/benchmarks/utils.py new file mode 100644 index 0000000000..0f8197c390 --- /dev/null +++ b/backend/geonature/tests/benchmarks/utils.py @@ -0,0 +1,44 @@ +import time +import logging + +import pytest +import pandas +from sqlalchemy import event + +from geonature.utils.env import db +from .benchmark_generator import CLater + +logging.basicConfig() +logger = logging.getLogger("logger-name") +logger.setLevel(logging.DEBUG) + + +@pytest.fixture(scope="class") +def activate_profiling_sql(): + """ + Fixture to activate profiling for SQL queries and storing query's statements and execution times in a csv file. + """ + + results_file = "sql_queries.csv" + df = pandas.DataFrame([], columns=["Query", "Total Time [s.]"]) + df.to_csv(results_file, mode="a", header=True, index=None, sep=";") + + # @event.listens_for(Engine, "before_cursor_execute") + def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): + conn.info.setdefault("query_start_time", []).append(time.time()) + logger.debug("Start Query: %s" % statement) + + # @event.listens_for(Engine, "after_cursor_execute") + def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): + total = time.time() - conn.info["query_start_time"].pop(-1) + logger.debug("Query Complete!") + logger.debug("Total Time: %f" % total) + if statement.startswith("SELECT"): + df = pandas.DataFrame([[statement, total]], columns=["Query", "Total Time"]) + df.to_csv(results_file, mode="a", header=False, index=None, sep=";") + + event.listen(db.engine, "before_cursor_execute", before_cursor_execute) + event.listen(db.engine, "after_cursor_execute", after_cursor_execute) + + +CLIENT_GET, CLIENT_POST = CLater("self.client.get"), CLater("self.client.post") diff --git a/backend/geonature/tests/test_benchmark.py b/backend/geonature/tests/test_benchmark.py deleted file mode 100644 index f5166c4ab4..0000000000 --- a/backend/geonature/tests/test_benchmark.py +++ /dev/null @@ -1,199 +0,0 @@ -from typing import Any, Dict, Sequence, Callable -from .utils import set_logged_user -from .fixtures import users -from .test_pr_occhab import stations -from flask import url_for -import pytest - -from .benchmark_data import * - - -class GetLatter: - def __init__(self, value) -> None: - self.value = value - - -class BenchmarkTest: - """ - Class that allows to define a benchmark test and generate the pytest function to run the benchmark. - - Example, in a pytest file: - ```python - import pytest - bench = BenchmarkTest(print,"test_print",["Hello","World"],{}) - @pytest.mark.usefixtures("client_class", "temporary_transaction") - class TestBenchie: - pass - TestBenchie.test_print = bench.generate_func_test() - ``` - - If a function or its argument depend on the pytest function context, use the GetLatter class : GetLatter("). For example, to use - the `url_for()` function, replace from `url_for(...)` to `GetLatter("url_for(...)")`. - - If the benchmark requires a user to be logged, use the `function_kwargs` with the "user_profile" key and the value corresponds to a key - available in the dictionary returned by the `user` fixture. - - - """ - - def __init__(self, function, name_benchmark, function_args=[], function_kwargs={}) -> None: - """ - Constructor of BenchmarkTest - - Parameters - ---------- - function : Callable | GetLatter - function that will be benchmark - name_benchmark : str - name of the benchmark - function_args : Sequence[Any | GetLatter] - args for the function - function_kwargs : Dict[str,Any] - kwargs for the function - """ - self.function = function - self.name_benchmark = name_benchmark - self.function_args = function_args - self.function_kwargs = function_kwargs - - def generate_func_test(self): - """ - Return the pytest function to run the benchmark on the indicated function. - - Returns - ------- - Callable - test function - - Raises - ------ - KeyError - if the user_profile given do not exists - """ - fixture = self.function_kwargs.pop("fixture", []) - user_profile = self.function_kwargs.pop("user_profile", None) - - func, args, kwargs = self.function, self.function_args, self.function_kwargs - - def function_to_include_fixture(*fixture): - - def final_test_function(self, benchmark, users): - - if user_profile: - if not user_profile in users: - raise KeyError(f"{user_profile} can't be found in the users fixture !") - set_logged_user(self.client, users[user_profile]) - benchmark( - eval(func.value) if isinstance(func, GetLatter) else func, - *[eval(arg.value) if isinstance(arg, GetLatter) else arg for arg in args], - **kwargs, - ) - - return final_test_function - - return function_to_include_fixture(*fixture) - - -from geonature.utils.env import db -from sqlalchemy import event -from sqlalchemy.engine import Engine -import time -import logging - -logging.basicConfig() -logger = logging.getLogger("logger-name") -logger.setLevel(logging.DEBUG) - -import pandas - - -@pytest.fixture(scope="class") -def activate_profiling_sql(): - """ - Fixture to activate profiling for SQL queries and storing query's statements and execution times in a csv file. - """ - - results_file = "sql_queries.csv" - df = pandas.DataFrame([], columns=["Query", "Total Time [s.]"]) - df.to_csv(results_file, mode="a", header=True, index=None, sep=";") - - # @event.listens_for(Engine, "before_cursor_execute") - def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): - conn.info.setdefault("query_start_time", []).append(time.time()) - logger.debug("Start Query: %s" % statement) - - # @event.listens_for(Engine, "after_cursor_execute") - def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): - total = time.time() - conn.info["query_start_time"].pop(-1) - logger.debug("Query Complete!") - logger.debug("Total Time: %f" % total) - if statement.startswith("SELECT"): - df = pandas.DataFrame([[statement, total]], columns=["Query", "Total Time"]) - df.to_csv(results_file, mode="a", header=False, index=None, sep=";") - - event.listen(db.engine, "before_cursor_execute", before_cursor_execute) - event.listen(db.engine, "after_cursor_execute", after_cursor_execute) - - -# r = self.client.post(url_for("gn_synthese.get_observations_for_web"), json=filters) -tests = [ - BenchmarkTest( - GetLatter("self.client.get"), - "get_station", - [GetLatter("""url_for("occhab.get_station", id_station=8)""")], - dict(user_profile="user", fixture=[stations]), - ), - BenchmarkTest( - GetLatter("self.client.get"), - "get_default_nomenclatures", - [GetLatter("""url_for("gn_synthese.getDefaultsNomenclatures")""")], - dict(user_profile="self_user"), - ), - BenchmarkTest( - GetLatter("self.client.post"), - "synthese_with_geometry_bbox", - [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], - dict(user_profile="admin_user", json=benchmark_synthese_intersection_data_test_bbox), - ), - BenchmarkTest( - GetLatter("self.client.post"), - "synthese_with_geometry_complex_poly", - [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], - dict( - user_profile="admin_user", - json=benchmark_synthese_intersection_data_test_complex_polygon, - ), - ), - BenchmarkTest( - GetLatter("self.client.post"), - "synthese_with_commune", - [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], - dict( - user_profile="admin_user", - json=benchmark_synthese_intersection_data_test_commune, - ), - ), - BenchmarkTest( - GetLatter("self.client.post"), - "synthese_with_up_tree_taxon", - [GetLatter("""url_for("gn_synthese.get_observations_for_web")""")], - dict( - user_profile="admin_user", - json=benchmark_synthese_with_tree_taxon, - ), - ), -] - - -@pytest.mark.usefixtures("client_class", "temporary_transaction", "activate_profiling_sql") -class TestBenchie: - pass - - -for test in tests: - setattr(TestBenchie, f"test_{test.name_benchmark}", test.generate_func_test()) - - -# def test_routes(app): -# for rule in app.url_map.iter_rules(): -# print(rule.endpoint) From 476259d6d099b7cccdb609f3e0451b15e194ad2e Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Thu, 22 Feb 2024 11:07:27 +0100 Subject: [PATCH 08/27] fix docstring + delete unused variable --- backend/geonature/tests/benchmarks/benchmark_generator.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/geonature/tests/benchmarks/benchmark_generator.py b/backend/geonature/tests/benchmarks/benchmark_generator.py index edd1f6cab7..4f80e2f483 100644 --- a/backend/geonature/tests/benchmarks/benchmark_generator.py +++ b/backend/geonature/tests/benchmarks/benchmark_generator.py @@ -22,7 +22,7 @@ class BenchmarkTest: @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchie: pass - TestBenchie.test_print = bench.generate_func_test() + TestBenchie.test_print = bench() ``` If a function or its argument depend on the pytest function context, use the GetLatter class : GetLatter("). For example, to use @@ -42,8 +42,6 @@ def __init__(self, function, function_args=[], function_kwargs={}) -> None: ---------- function : Callable | GetLatter function that will be benchmark - name_benchmark : str - name of the benchmark function_args : Sequence[Any | GetLatter] args for the function function_kwargs : Dict[str,Any] @@ -73,7 +71,6 @@ def generate_func_test(self): fixtures = self.function_kwargs.pop("fixtures", []) user_profile = self.function_kwargs.pop("user_profile", None) - imports = self.function_kwargs.pop("imports", []) func, args, kwargs = self.function, self.function_args, self.function_kwargs From f38036ea6381435e4e525c4e7b27ab2a2667a00f Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Mon, 4 Mar 2024 15:24:36 +0100 Subject: [PATCH 09/27] Change benchmark data (work with ecrin data) --- .../tests/benchmarks/benchmark_data.py | 116 +++++++++--------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/backend/geonature/tests/benchmarks/benchmark_data.py b/backend/geonature/tests/benchmarks/benchmark_data.py index 67e3faff9e..75abbc17b7 100644 --- a/backend/geonature/tests/benchmarks/benchmark_data.py +++ b/backend/geonature/tests/benchmarks/benchmark_data.py @@ -12,11 +12,11 @@ "type": "Polygon", "coordinates": [ [ - [6.363466, 44.754607], - [6.363466, 44.892886], - [6.519971, 44.892886], - [6.519971, 44.754607], - [6.363466, 44.754607], + [5.828785, 44.632571], + [5.828785, 45.06773], + [6.625493, 45.06773], + [6.625493, 44.632571], + [5.828785, 44.632571], ] ], }, @@ -32,61 +32,57 @@ "type": "Polygon", "coordinates": [ [ - [6.373232, 44.894802], - [6.363622, 44.871455], - [6.352639, 44.846151], - [6.356758, 44.843231], - [6.381469, 44.838363], - [6.381572, 44.833973], - [6.376767, 44.817419], - [6.367158, 44.814984], - [6.353429, 44.814497], - [6.352743, 44.800373], - [6.365098, 44.792579], - [6.374022, 44.794527], - [6.377454, 44.783322], - [6.369903, 44.772114], - [6.334895, 44.773089], - [6.321167, 44.774063], - [6.311557, 44.764316], - [6.345192, 44.75603], - [6.359607, 44.746279], - [6.356175, 44.739453], - [6.37814, 44.729213], - [6.41864, 44.755055], - [6.438546, 44.753105], - [6.44541, 44.741404], - [6.463944, 44.744329], - [6.419326, 44.723848], - [6.439919, 44.714092], - [6.504443, 44.726774], - [6.549061, 44.755542], - [6.573772, 44.783809], - [6.540137, 44.816932], - [6.55112, 44.825209], - [6.542883, 44.847115], - [6.569654, 44.861228], - [6.547001, 44.885552], - [6.549061, 44.89771], - [6.510621, 44.922019], - [6.500324, 44.909379], - [6.494833, 44.884093], - [6.470122, 44.882633], - [6.461198, 44.904517], - [6.480418, 44.931253], - [6.476986, 44.946803], - [6.400106, 44.940486], - [6.41864, 44.919588], - [6.42619, 44.915213], - [6.411089, 44.890902], - [6.38775, 44.922991], - [6.340387, 44.929309], - [6.306752, 44.91181], - [6.286846, 44.896737], - [6.304006, 44.887497], - [6.343819, 44.885552], - [6.36098, 44.892847], - [6.373232, 44.894802], + [6.063715, 44.968659], + [5.974429, 44.981293], + [5.955198, 44.934631], + [5.98256, 44.890826], + [6.040252, 44.867462], + [6.085582, 44.860646], + [6.125418, 44.884012], + [6.141901, 44.876225], + [6.135038, 44.837262], + [6.099322, 44.820697], + [6.064979, 44.804128], + [6.070473, 44.750493], + [6.133664, 44.731947], + [6.176247, 44.756341], + [6.189983, 44.777799], + [6.19273, 44.731947], + [6.261412, 44.737803], + [6.273775, 44.760243], + [6.319105, 44.741706], + [6.279269, 44.688002], + [6.231192, 44.656741], + [6.483941, 44.672375], + [6.396028, 44.628395], + [6.486688, 44.596121], + [6.538887, 44.655764], + [6.533392, 44.688981], + [6.500425, 44.73877], + [6.533392, 44.748528], + [6.514161, 44.790467], + [6.41938, 44.812887], + [6.488062, 44.854779], + [6.536139, 44.856727], + [6.597938, 44.894757], + [6.518262, 44.934648], + [6.437214, 44.932702], + [6.391881, 44.883076], + [6.356164, 44.86068], + [6.251768, 44.874311], + [6.284735, 44.921996], + [6.317703, 44.952143], + [6.385011, 44.964781], + [6.364406, 44.972557], + [6.449572, 44.976444], + [6.479792, 44.984218], + [6.468803, 45.027928], + [6.413857, 45.043461], + [6.336928, 45.055017], + [6.194069, 45.048223], + [6.170716, 44.952049], + [6.161099, 44.949131], + [6.063715, 44.968659], ] ], }, From 2220699ace058ffecde9c8fa2cd671b7c3e375e2 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Mon, 4 Mar 2024 15:25:01 +0100 Subject: [PATCH 10/27] add user to test blurring performance --- backend/geonature/tests/fixtures.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index a4785b2f63..d1dfe52da4 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -334,10 +334,18 @@ def create_user( 2, False, [], - {"C": 2, "OCCHAB": {"R": 2, "U": 1, "E": 2, "D": 1}}, + { + "C": 2, + "OCCHAB": {"R": 2, "U": 1, "E": 2, "D": 1}, + "OCCTAX": {"R": 2, "U": 1, "E": 2, "D": 1}, + }, ), {}, ), + ( + ("user_with_blurring", organisme, 2, True, [], {}), + {}, + ), ] for (username, *args), kwargs in users_to_create: From ba5c1a1a3a6ed26108adf1ce93ad7a4a94605f26 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Mon, 4 Mar 2024 15:25:28 +0100 Subject: [PATCH 11/27] add performance test of occhab --- .../tests/benchmarks/test_benchmark_occhab.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py index 5c6fd4f605..9bda88c856 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py @@ -15,8 +15,20 @@ @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchmarkOcchab: - test_get_station = BenchmarkTest( + test_occhab_get_station = BenchmarkTest( CLIENT_GET, [CLater("""url_for("occhab.get_station", id_station=8)""")], dict(user_profile="user", fixtures=[stations]), )() + + test_occhab_list_stations = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("occhab.list_stations")""")], + dict(user_profile="admin_user", fixtures=[]), + )() + + test_occhab_list_stations_restricted = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("occhab.list_stations")""")], + dict(user_profile="user_restricted_occhab", fixtures=[]), + )() From f44abbe1df33cc9e7c98a0820afbe6241826282b Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Mon, 4 Mar 2024 15:25:43 +0100 Subject: [PATCH 12/27] add performance for occtax --- .../tests/benchmarks/test_benchmark_occtax.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 backend/geonature/tests/benchmarks/test_benchmark_occtax.py diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py new file mode 100644 index 0000000000..e6e57058ff --- /dev/null +++ b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py @@ -0,0 +1,27 @@ +import logging +import pytest +from geonature.tests.benchmarks import * +from geonature.tests.test_pr_occhab import stations + +from .benchmark_generator import BenchmarkTest, CLater + +logging.basicConfig() +logger = logging.getLogger("logger-name") +logger.setLevel(logging.DEBUG) + +from .utils import CLIENT_GET, CLIENT_POST + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestBenchmarkOcctax: + + test_occtax_list_releves_restricted = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("pr_occtax.getReleves")""")], + dict(user_profile="user_restricted_occhab", fixtures=[]), + )() + test_occtax_list_releves_unrestricted = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("pr_occtax.getReleves")""")], + dict(user_profile="admin_user", fixtures=[]), + )() From f8c0be6b3bc9e8e14db6d95ec512318392a5d7f7 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Mon, 4 Mar 2024 15:25:59 +0100 Subject: [PATCH 13/27] add performance test in synthese with blurring --- .../benchmarks/test_benchmark_synthese.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py index e80cb48974..726ef92f61 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py @@ -27,6 +27,7 @@ class TestBenchmarkSynthese: json=benchmark_synthese_intersection_data_test_bbox, ), )() + test_synthese_with_geometry_complex_poly = BenchmarkTest( CLIENT_POST, [CLater("""url_for("gn_synthese.get_observations_for_web")""")], @@ -68,3 +69,55 @@ class TestBenchmarkSynthese: json=benchmark_synthese_with_tree_taxon, ), )() + + ### WITH BLURRING + test_synthese_with_geometry_bbox_with_blurring = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="user_with_blurring", + json=benchmark_synthese_intersection_data_test_bbox, + ), + )() + + test_synthese_with_geometry_complex_poly_with_blurring = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="user_with_blurring", + json=benchmark_synthese_intersection_data_test_complex_polygon, + ), + )() + test_synthese_with_commune_with_blurring = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="user_with_blurring", + json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_commune()"), + ), + )() + + test_synthese_with_departement_with_blurring = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="user_with_blurring", + json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_departement()"), + ), + )() + test_synthese_with_region_with_blurring = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="user_with_blurring", + json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_region()"), + ), + )() + test_synthese_with_up_tree_taxon_with_blurring = BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + dict( + user_profile="user_with_blurring", + json=benchmark_synthese_with_tree_taxon, + ), + )() From c064b930ddc3959fd80000a81347a844af03cfc0 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 5 Mar 2024 11:40:55 +0100 Subject: [PATCH 14/27] update ci to run benchmarks only in eval_perf --- .github/workflows/eval_perf.yml | 22 +++++++++++----------- .github/workflows/pytest.yml | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/eval_perf.yml b/.github/workflows/eval_perf.yml index f9f5134659..d44f84e2c6 100644 --- a/.github/workflows/eval_perf.yml +++ b/.github/workflows/eval_perf.yml @@ -10,16 +10,16 @@ jobs: strategy: fail-fast: false matrix: - debian-version: ["11", "12"] + debian-version: ['11', '12'] include: - - debian-version: "11" - python-version: "3.9" - postgres-version: "13" - postgis-version: "3.2" - - debian-version: "12" - python-version: "3.11" - postgres-version: "15" - postgis-version: "3.3" + - debian-version: '11' + python-version: '3.9' + postgres-version: '13' + postgis-version: '3.2' + - debian-version: '12' + python-version: '3.11' + postgres-version: '15' + postgis-version: '3.3' name: Debian ${{ matrix.debian-version }} @@ -51,7 +51,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: "pip" + cache: 'pip' - name: Install GDAL run: | sudo apt update @@ -116,7 +116,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Test with pytest run: | - pytest backend/geonature/tests/test_benchmark.py + pytest --benchmark-only env: GEONATURE_CONFIG_FILE: config/test_config.toml # https://stackoverflow.com/a/64126737 For posting results on GitHub Pull Requests diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index c9de492237..32c6b218c4 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -19,16 +19,16 @@ jobs: strategy: fail-fast: false matrix: - debian-version: ["11", "12"] + debian-version: ['11', '12'] include: - - debian-version: "11" - python-version: "3.9" - postgres-version: "13" - postgis-version: "3.2" - - debian-version: "12" - python-version: "3.11" - postgres-version: "15" - postgis-version: "3.3" + - debian-version: '11' + python-version: '3.9' + postgres-version: '13' + postgis-version: '3.2' + - debian-version: '12' + python-version: '3.11' + postgres-version: '15' + postgis-version: '3.3' name: Debian ${{ matrix.debian-version }} @@ -60,7 +60,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: "pip" + cache: 'pip' - name: Install GDAL run: | sudo apt update @@ -122,7 +122,7 @@ jobs: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Test with pytest run: | - pytest -v --cov --cov-report xml + pytest -v --cov --cov-report xml --benchmark-skip env: GEONATURE_CONFIG_FILE: config/test_config.toml - name: Upload coverage to Codecov From 68838f949d247a2d9e9b486ce897e4675970e7f7 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 5 Mar 2024 11:41:20 +0100 Subject: [PATCH 15/27] add user with blurring in fixture --- backend/geonature/tests/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index d1dfe52da4..569b708732 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -343,7 +343,7 @@ def create_user( {}, ), ( - ("user_with_blurring", organisme, 2, True, [], {}), + ("user_with_blurring", organisme, 1, True, [], {}), {}, ), ] From d23755f3f5b0589d6d331ef5fa0e70c3f4e32ef9 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 5 Mar 2024 11:41:47 +0100 Subject: [PATCH 16/27] add blurring to synthese benchmark --- .../geonature/tests/benchmarks/__init__.py | 2 + .../benchmarks/test_benchmark_synthese.py | 115 ++++++++++-------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/backend/geonature/tests/benchmarks/__init__.py b/backend/geonature/tests/benchmarks/__init__.py index dba8d97bde..86fd72efad 100644 --- a/backend/geonature/tests/benchmarks/__init__.py +++ b/backend/geonature/tests/benchmarks/__init__.py @@ -1,3 +1,5 @@ # Import required for CLater class when using eval() from flask import url_for from .benchmark_data import * +from geonature.core.gn_synthese.models import Synthese +from sqlalchemy import select diff --git a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py index 726ef92f61..3c34ff3e34 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py @@ -1,7 +1,9 @@ import logging +from geonature.tests.test_synthese import blur_sensitive_observations import pytest from geonature.tests.benchmarks import * from geonature.tests.test_pr_occhab import stations +from geonature.core.gn_synthese.models import Synthese from .benchmark_generator import BenchmarkTest, CLater @@ -11,113 +13,118 @@ from .utils import CLIENT_GET, CLIENT_POST +SYNTHESE_GET_OBS_URL = """url_for("gn_synthese.get_observations_for_web")""" +SYNTHESE_EXPORT_OBS_URL = """url_for("gn_synthese.export_observations_web")""" -@pytest.mark.usefixtures("client_class", "temporary_transaction") # , "activate_profiling_sql") + +@pytest.mark.usefixtures( + "client_class", + "temporary_transaction", +) # , "activate_profiling_sql") class TestBenchmarkSynthese: + # GET NOMENCLATURE test_get_default_nomenclatures = BenchmarkTest( CLIENT_GET, [CLater("""url_for("gn_synthese.getDefaultsNomenclatures")""")], dict(user_profile="self_user"), - )() + ) + test_synthese_with_geometry_bbox = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_GET_OBS_URL)], dict( user_profile="admin_user", json=benchmark_synthese_intersection_data_test_bbox, ), - )() + ) test_synthese_with_geometry_complex_poly = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_GET_OBS_URL)], dict( user_profile="admin_user", json=benchmark_synthese_intersection_data_test_complex_polygon, ), - )() + ) test_synthese_with_commune = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_GET_OBS_URL)], dict( user_profile="admin_user", json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_commune()"), ), - )() + ) test_synthese_with_departement = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_GET_OBS_URL)], dict( user_profile="admin_user", json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_departement()"), ), - )() + ) test_synthese_with_region = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_GET_OBS_URL)], dict( user_profile="admin_user", json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_region()"), ), - )() + ) test_synthese_with_up_tree_taxon = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_GET_OBS_URL)], dict( user_profile="admin_user", json=benchmark_synthese_with_tree_taxon, ), - )() + ) - ### WITH BLURRING - test_synthese_with_geometry_bbox_with_blurring = BenchmarkTest( + # EXPORT TESTING + test_synthese_export_1000 = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_EXPORT_OBS_URL)], dict( - user_profile="user_with_blurring", - json=benchmark_synthese_intersection_data_test_bbox, + user_profile="admin_user", + json=CLater("db.session.execute(select(Synthese.id_synthese).limit(1000)).all()"), ), - )() + ) - test_synthese_with_geometry_complex_poly_with_blurring = BenchmarkTest( + test_synthese_export_10000 = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_EXPORT_OBS_URL)], dict( - user_profile="user_with_blurring", - json=benchmark_synthese_intersection_data_test_complex_polygon, - ), - )() - test_synthese_with_commune_with_blurring = BenchmarkTest( - CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], - dict( - user_profile="user_with_blurring", - json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_commune()"), + user_profile="admin_user", + json=CLater("db.session.execute(select(Synthese.id_synthese).limit(10000)).all()"), ), - )() + ) - test_synthese_with_departement_with_blurring = BenchmarkTest( - CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], - dict( - user_profile="user_with_blurring", - json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_departement()"), - ), - )() - test_synthese_with_region_with_blurring = BenchmarkTest( + test_synthese_export_100000 = BenchmarkTest( CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], + [CLater(SYNTHESE_EXPORT_OBS_URL)], dict( - user_profile="user_with_blurring", - json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_region()"), - ), - )() - test_synthese_with_up_tree_taxon_with_blurring = BenchmarkTest( - CLIENT_POST, - [CLater("""url_for("gn_synthese.get_observations_for_web")""")], - dict( - user_profile="user_with_blurring", - json=benchmark_synthese_with_tree_taxon, + user_profile="admin_user", + json=CLater("db.session.execute(select(Synthese.id_synthese).limit(100000)).all()"), ), - )() + ) + + +# Add blurring +for attr in dir(TestBenchmarkSynthese): + if attr.startswith("test_"): + b_test = getattr(TestBenchmarkSynthese, attr) + kwargs = b_test.function_kwargs + if "fixture" in kwargs: + kwargs["fixtures"].append(blur_sensitive_observations) + else: + kwargs["fixtures"] = [blur_sensitive_observations] + setattr( + TestBenchmarkSynthese, + f"{attr}_with_blurring", + BenchmarkTest(b_test.function, b_test.function_args, kwargs)(), + ) + setattr( + TestBenchmarkSynthese, + attr, + b_test(), + ) From c069f3d6423036cf288f7606d07c16750bbb8fe7 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 5 Mar 2024 16:23:22 +0100 Subject: [PATCH 17/27] add method to include blurring test automatically --- .../benchmarks/test_benchmark_synthese.py | 26 +++-------- backend/geonature/tests/benchmarks/utils.py | 43 ++++++++++++++++++- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py index 3c34ff3e34..c8cbc252b3 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py @@ -1,5 +1,5 @@ import logging -from geonature.tests.test_synthese import blur_sensitive_observations + import pytest from geonature.tests.benchmarks import * from geonature.tests.test_pr_occhab import stations @@ -7,16 +7,18 @@ from .benchmark_generator import BenchmarkTest, CLater + logging.basicConfig() logger = logging.getLogger("logger-name") logger.setLevel(logging.DEBUG) -from .utils import CLIENT_GET, CLIENT_POST +from .utils import CLIENT_GET, CLIENT_POST, add_bluring_to_benchmark_test_class SYNTHESE_GET_OBS_URL = """url_for("gn_synthese.get_observations_for_web")""" SYNTHESE_EXPORT_OBS_URL = """url_for("gn_synthese.export_observations_web")""" +@pytest.mark.benchmark(group="synthese") @pytest.mark.usefixtures( "client_class", "temporary_transaction", @@ -109,22 +111,4 @@ class TestBenchmarkSynthese: ) -# Add blurring -for attr in dir(TestBenchmarkSynthese): - if attr.startswith("test_"): - b_test = getattr(TestBenchmarkSynthese, attr) - kwargs = b_test.function_kwargs - if "fixture" in kwargs: - kwargs["fixtures"].append(blur_sensitive_observations) - else: - kwargs["fixtures"] = [blur_sensitive_observations] - setattr( - TestBenchmarkSynthese, - f"{attr}_with_blurring", - BenchmarkTest(b_test.function, b_test.function_args, kwargs)(), - ) - setattr( - TestBenchmarkSynthese, - attr, - b_test(), - ) +add_bluring_to_benchmark_test_class(TestBenchmarkSynthese) diff --git a/backend/geonature/tests/benchmarks/utils.py b/backend/geonature/tests/benchmarks/utils.py index 0f8197c390..83b40fb0b7 100644 --- a/backend/geonature/tests/benchmarks/utils.py +++ b/backend/geonature/tests/benchmarks/utils.py @@ -6,7 +6,8 @@ from sqlalchemy import event from geonature.utils.env import db -from .benchmark_generator import CLater +from .benchmark_generator import CLater, BenchmarkTest +from geonature.tests.test_synthese import blur_sensitive_observations logging.basicConfig() logger = logging.getLogger("logger-name") @@ -41,4 +42,44 @@ def after_cursor_execute(conn, cursor, statement, parameters, context, executema event.listen(db.engine, "after_cursor_execute", after_cursor_execute) +def add_bluring_to_benchmark_test_class(benchmark_cls: type): + """ + Add the blurring enabling fixture to all benchmark tests declared in the given class. + + Parameters + ---------- + benchmark_cls : type + benchmark test class + """ + for attr in dir(benchmark_cls): + if attr.startswith("test_"): + b_test = getattr(benchmark_cls, attr) + + # If attribute does not corresponds to a BenchmarkTest, skip + if not isinstance(b_test, BenchmarkTest): + continue + + # Include blurring fixture + kwargs = b_test.function_kwargs + kwargs["fixtures"] = ( + kwargs["fixtures"] + [blur_sensitive_observations] + if "fixtures" in kwargs + else [blur_sensitive_observations] + ) + # Recreate BenchmarkTest object including the blurring enabling fixture + setattr( + benchmark_cls, + f"{attr}_with_blurring", + BenchmarkTest( + b_test.function, b_test.function_args, kwargs + )(), # Run the test function generation while we're at it + ) + # Generate the test function from the orginal `BenchmarkTest`s + setattr( + benchmark_cls, + attr, + b_test(), + ) + + CLIENT_GET, CLIENT_POST = CLater("self.client.get"), CLater("self.client.post") From 0bb6b19e8eba492d3acb504363a841feb18bd5b3 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Tue, 5 Mar 2024 16:23:41 +0100 Subject: [PATCH 18/27] add group to benchmark --- .github/workflows/eval_perf.yml | 58 +++++++++++++------ .../tests/benchmarks/test_benchmark_occhab.py | 1 + .../tests/benchmarks/test_benchmark_occtax.py | 1 + 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/.github/workflows/eval_perf.yml b/.github/workflows/eval_perf.yml index d44f84e2c6..968f264605 100644 --- a/.github/workflows/eval_perf.yml +++ b/.github/workflows/eval_perf.yml @@ -1,7 +1,15 @@ -name: Routes performance evaluation - +name: Performance Evaluation on: - workflow_dispatch: + push: + branches: + - master + - hotfixes + - develop + pull_request: + branches: + - master + - hotfixes + - develop jobs: build: @@ -79,21 +87,33 @@ jobs: geonature db status --dependencies env: GEONATURE_CONFIG_FILE: config/test_config.toml - - name: Restore database + # - name: Restore database + # run: | + # # wget https://www.dropbox.com/scl/fi/17gsthsftfg59mxwmbbre/export_geonature_10000.zip?rlkey=33choleag4xw60wadm802c3oh&dl=1 -O 10kDump.zip + # # unzip 10kDump.zip + # wget https://www.dropbox.com/scl/fi/jjkxyg120bxc0dp8uy8kq/300KDump.sql?rlkey=tyuk2svitcb9nyshn7r09yo7b&dl=1 -O 300KDump.sql + # ls + # psql -h localhost -U geonatadmin -d geonature2db -f 300KDump.sql + # env: + # PGPASSWORD: geonatpasswd + - name: Install database run: | - # wget https://www.dropbox.com/scl/fi/17gsthsftfg59mxwmbbre/export_geonature_10000.zip?rlkey=33choleag4xw60wadm802c3oh&dl=1 -O 10kDump.zip - # unzip 10kDump.zip - wget https://www.dropbox.com/scl/fi/jjkxyg120bxc0dp8uy8kq/300KDump.sql?rlkey=tyuk2svitcb9nyshn7r09yo7b&dl=1 -O 300KDump.sql - ls - psql -h localhost -U geonatadmin -d geonature2db -f 300KDump.sql + install/03b_populate_db.sh env: - PGPASSWORD: geonatpasswd - - name: Install module import + GEONATURE_CONFIG_FILE: config/test_config.toml + srid_local: 2154 + install_bdc_statuts: true + add_sample_data: true + install_sig_layers: true + install_grid_layer_5: true + install_grid_layer_10: true + install_ref_sensitivity: true + - name: Show database status run: | - wget https://github.com/PnX-SI/gn_module_import/archive/refs/heads/develop.zip - unzip develop.zip - cd gn_module_import-develop - pip install -e . + geonature db status + env: + GEONATURE_CONFIG_FILE: config/test_config.toml + - name: Install core modules backend run: | pip install -e contrib/occtax @@ -114,9 +134,13 @@ jobs: geonature db status --dependencies env: GEONATURE_CONFIG_FILE: config/test_config.toml - - name: Test with pytest + - name: Load benchmark stable data + run: | + wget https://geonature.fr/data/benchmark_history/benchmark_stable.json -O benchmark_stable.json + + - name: Compare performance to stable data run: | - pytest --benchmark-only + pytest --benchmark-only --benchmark-compare-fail="mean:0.1" --benchmark-compare=benchmark_stable.json env: GEONATURE_CONFIG_FILE: config/test_config.toml # https://stackoverflow.com/a/64126737 For posting results on GitHub Pull Requests diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py index 9bda88c856..173d875579 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py @@ -12,6 +12,7 @@ from .utils import CLIENT_GET, CLIENT_POST +@pytest.mark.benchmark(group="occhab") @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchmarkOcchab: diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py index e6e57058ff..9123dbaebe 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py @@ -12,6 +12,7 @@ from .utils import CLIENT_GET, CLIENT_POST +@pytest.mark.benchmark(group="occtax") @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchmarkOcctax: From acbfeb8559698e57d46971dd7ed20e9bafccf420 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Thu, 7 Mar 2024 16:35:04 +0100 Subject: [PATCH 19/27] add status and taxons export to synthese benchmark --- .../benchmarks/test_benchmark_synthese.py | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py index c8cbc252b3..7aecce662d 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py @@ -16,6 +16,8 @@ SYNTHESE_GET_OBS_URL = """url_for("gn_synthese.get_observations_for_web")""" SYNTHESE_EXPORT_OBS_URL = """url_for("gn_synthese.export_observations_web")""" +SYNTHESE_EXPORT_STATUS_URL = """url_for("gn_synthese.export_status")""" +SYNTHESE_EXPORT_TAXON_WEB_URL = """url_for("gn_synthese.export_taxon_web")""" @pytest.mark.benchmark(group="synthese") @@ -31,7 +33,7 @@ class TestBenchmarkSynthese: dict(user_profile="self_user"), ) - test_synthese_with_geometry_bbox = BenchmarkTest( + test_with_geometry_bbox = BenchmarkTest( CLIENT_POST, [CLater(SYNTHESE_GET_OBS_URL)], dict( @@ -40,7 +42,7 @@ class TestBenchmarkSynthese: ), ) - test_synthese_with_geometry_complex_poly = BenchmarkTest( + test_with_geometry_complex_poly = BenchmarkTest( CLIENT_POST, [CLater(SYNTHESE_GET_OBS_URL)], dict( @@ -48,7 +50,7 @@ class TestBenchmarkSynthese: json=benchmark_synthese_intersection_data_test_complex_polygon, ), ) - test_synthese_with_commune = BenchmarkTest( + test_with_commune = BenchmarkTest( CLIENT_POST, [CLater(SYNTHESE_GET_OBS_URL)], dict( @@ -57,7 +59,7 @@ class TestBenchmarkSynthese: ), ) - test_synthese_with_departement = BenchmarkTest( + test_with_departement = BenchmarkTest( CLIENT_POST, [CLater(SYNTHESE_GET_OBS_URL)], dict( @@ -65,7 +67,7 @@ class TestBenchmarkSynthese: json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_departement()"), ), ) - test_synthese_with_region = BenchmarkTest( + test_with_region = BenchmarkTest( CLIENT_POST, [CLater(SYNTHESE_GET_OBS_URL)], dict( @@ -73,7 +75,7 @@ class TestBenchmarkSynthese: json=CLater("benchmark_data.benchmark_synthese_intersection_data_test_region()"), ), ) - test_synthese_with_up_tree_taxon = BenchmarkTest( + test_with_up_tree_taxon = BenchmarkTest( CLIENT_POST, [CLater(SYNTHESE_GET_OBS_URL)], dict( @@ -82,33 +84,27 @@ class TestBenchmarkSynthese: ), ) - # EXPORT TESTING - test_synthese_export_1000 = BenchmarkTest( - CLIENT_POST, - [CLater(SYNTHESE_EXPORT_OBS_URL)], - dict( - user_profile="admin_user", - json=CLater("db.session.execute(select(Synthese.id_synthese).limit(1000)).all()"), - ), - ) - - test_synthese_export_10000 = BenchmarkTest( - CLIENT_POST, - [CLater(SYNTHESE_EXPORT_OBS_URL)], - dict( - user_profile="admin_user", - json=CLater("db.session.execute(select(Synthese.id_synthese).limit(10000)).all()"), - ), - ) - - test_synthese_export_100000 = BenchmarkTest( - CLIENT_POST, - [CLater(SYNTHESE_EXPORT_OBS_URL)], - dict( - user_profile="admin_user", - json=CLater("db.session.execute(select(Synthese.id_synthese).limit(100000)).all()"), - ), - ) +# EXPORT TESTING +for url, label in [ + (SYNTHESE_EXPORT_STATUS_URL, "status"), + (SYNTHESE_EXPORT_TAXON_WEB_URL, "taxons"), + (SYNTHESE_EXPORT_OBS_URL, "observations"), +]: + for n_obs in [1000, 10000, 100000, 1000000]: + setattr( + TestBenchmarkSynthese, + f"test_export_{label}_{n_obs}", + BenchmarkTest( + CLIENT_POST, + [CLater(SYNTHESE_EXPORT_OBS_URL)], + dict( + user_profile="admin_user", + json=CLater( + f"db.session.execute(select(Synthese.id_synthese).limit({n_obs})).all()" + ), + ), + ), + ) add_bluring_to_benchmark_test_class(TestBenchmarkSynthese) From 9c4ad84471ba1090308cf6da38e6ab604445f243 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Thu, 7 Mar 2024 16:36:34 +0100 Subject: [PATCH 20/27] rename test in occtax --- backend/geonature/tests/benchmarks/test_benchmark_occtax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py index 9123dbaebe..a27f16e32e 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py @@ -16,12 +16,12 @@ @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchmarkOcctax: - test_occtax_list_releves_restricted = BenchmarkTest( + test_list_releves_restricted = BenchmarkTest( CLIENT_GET, [CLater("""url_for("pr_occtax.getReleves")""")], dict(user_profile="user_restricted_occhab", fixtures=[]), )() - test_occtax_list_releves_unrestricted = BenchmarkTest( + test_list_releves_unrestricted = BenchmarkTest( CLIENT_GET, [CLater("""url_for("pr_occtax.getReleves")""")], dict(user_profile="admin_user", fixtures=[]), From f43ef69326b3177a810d099a56192e13e82053b8 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Thu, 7 Mar 2024 16:36:54 +0100 Subject: [PATCH 21/27] add benchmark test for gn_meta --- .../benchmarks/test_benchmark_gn_meta.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py diff --git a/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py b/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py new file mode 100644 index 0000000000..51242bd4a2 --- /dev/null +++ b/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py @@ -0,0 +1,28 @@ +import logging +import pytest +from geonature.tests.benchmarks import * +from geonature.tests.test_pr_occhab import stations + +from .benchmark_generator import BenchmarkTest, CLater + +logging.basicConfig() +logger = logging.getLogger("logger-name") +logger.setLevel(logging.DEBUG) + +from .utils import CLIENT_GET, CLIENT_POST + + +@pytest.mark.benchmark(group="gn_meta") +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestBenchmarkGnMeta: + + test_list_acquisition_frameworks = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("gn_meta.get_acquisition_frameworks_list")""")], + dict(user_profile="admin_user", fixtures=[]), + )() + test_list_datasets = BenchmarkTest( + CLIENT_GET, + [CLater("""url_for("gn_meta.get_datasets")""")], + dict(user_profile="admin_user", fixtures=[]), + )() From c576bcf9253c6a170596aa7d8f4f6b7fd45e99c0 Mon Sep 17 00:00:00 2001 From: Jacobe2169 Date: Thu, 7 Mar 2024 16:41:36 +0100 Subject: [PATCH 22/27] add export habitat benchmark test --- .../tests/benchmarks/test_benchmark_occhab.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py index 173d875579..5b8e920233 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py @@ -16,20 +16,32 @@ @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchmarkOcchab: - test_occhab_get_station = BenchmarkTest( + test_get_station = BenchmarkTest( CLIENT_GET, [CLater("""url_for("occhab.get_station", id_station=8)""")], dict(user_profile="user", fixtures=[stations]), )() - test_occhab_list_stations = BenchmarkTest( + test_list_stations = BenchmarkTest( CLIENT_GET, [CLater("""url_for("occhab.list_stations")""")], dict(user_profile="admin_user", fixtures=[]), )() - test_occhab_list_stations_restricted = BenchmarkTest( + test_list_stations_restricted = BenchmarkTest( CLIENT_GET, [CLater("""url_for("occhab.list_stations")""")], dict(user_profile="user_restricted_occhab", fixtures=[]), )() + + +for format_ in "csv geojson shapefile".split(): + setattr( + TestBenchmarkOcchab, + f"test_export_all_habitats_{format_}", + BenchmarkTest( + CLIENT_POST, + [CLater("""url_for("occhab.export_all_habitats",export_format="csv")""")], + dict(user_profile="admin_user", fixtures=[]), + )(), + ) From 956763463412f71ffd4e7931b8791fd8a40c405e Mon Sep 17 00:00:00 2001 From: Pierre Narcisi Date: Tue, 26 Mar 2024 15:09:37 +0100 Subject: [PATCH 23/27] Feat(CI) run on self hosted --- .github/workflows/eval_perf.yml | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/eval_perf.yml b/.github/workflows/eval_perf.yml index 968f264605..c6fbe44030 100644 --- a/.github/workflows/eval_perf.yml +++ b/.github/workflows/eval_perf.yml @@ -1,33 +1,24 @@ name: Performance Evaluation on: - push: - branches: - - master - - hotfixes - - develop - pull_request: - branches: - - master - - hotfixes - - develop + workflow_dispatch: jobs: build: - runs-on: ubuntu-latest + runs-on: self-hosted strategy: fail-fast: false matrix: - debian-version: ['11', '12'] + debian-version: ["11", "12"] include: - - debian-version: '11' - python-version: '3.9' - postgres-version: '13' - postgis-version: '3.2' - - debian-version: '12' - python-version: '3.11' - postgres-version: '15' - postgis-version: '3.3' + - debian-version: "11" + python-version: "3.9" + postgres-version: "13" + postgis-version: "3.2" + - debian-version: "12" + python-version: "3.11" + postgres-version: "15" + postgis-version: "3.3" name: Debian ${{ matrix.debian-version }} @@ -59,7 +50,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: 'pip' + cache: "pip" - name: Install GDAL run: | sudo apt update From 295c628070f1c77df082e27fffc2502fe53a63b4 Mon Sep 17 00:00:00 2001 From: Pierre Narcisi Date: Tue, 26 Mar 2024 15:12:40 +0100 Subject: [PATCH 24/27] Feat(CI) read eval.yml --- .github/workflows/eval.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml index 0089b4061d..8c80e6fe4d 100644 --- a/.github/workflows/eval.yml +++ b/.github/workflows/eval.yml @@ -4,21 +4,21 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: self-hosted strategy: fail-fast: false matrix: - debian-version: ["11", "12"] + debian-version: ['11', '12'] include: - - debian-version: "11" - python-version: "3.9" - postgres-version: "13" - postgis-version: "3.2" - - debian-version: "12" - python-version: "3.11" - postgres-version: "15" - postgis-version: "3.3" + - debian-version: '11' + # python-version: "3.9" + postgres-version: '13' + postgis-version: '3.2' + - debian-version: '12' + # python-version: "3.11" + postgres-version: '15' + postgis-version: '3.3' name: Debian ${{ matrix.debian-version }} @@ -50,7 +50,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: "pip" + cache: 'pip' - name: Install GDAL run: | sudo apt update From 96a8156f75a0ce756cc7c9a3492fe84931d0a0bb Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 26 Mar 2024 16:51:31 +0100 Subject: [PATCH 25/27] (test,eval_perf)add sql log filename in pytest --- backend/geonature/tests/benchmarks/utils.py | 30 ++++++++++++++++----- backend/geonature/tests/conftest.py | 10 +++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/backend/geonature/tests/benchmarks/utils.py b/backend/geonature/tests/benchmarks/utils.py index 83b40fb0b7..fe33ccdafb 100644 --- a/backend/geonature/tests/benchmarks/utils.py +++ b/backend/geonature/tests/benchmarks/utils.py @@ -1,5 +1,6 @@ import time import logging +import os import pytest import pandas @@ -14,17 +15,32 @@ logger.setLevel(logging.DEBUG) -@pytest.fixture(scope="class") -def activate_profiling_sql(): +@pytest.fixture(scope="session") +def activate_profiling_sql(sqllogfilename: pytest.FixtureDef): """ - Fixture to activate profiling for SQL queries and storing query's statements and execution times in a csv file. + Fixture to activate profiling for SQL queries and store query's statements and execution times in a CSV file. + + This fixture takes a `sqllogfilename` parameter, which is the path to a CSV file where the query statements and + execution times will be stored. If no `sqllogfilename` is provided, the SQL profiling will not be activated. + + Parameters + ---------- + - sqllogfilename: pytest.FixtureDef + The path to the CSV file where the query statements and execution times will be stored. + """ - results_file = "sql_queries.csv" + if not sqllogfilename: + logger.debug("No SQL Log file provided. SQL Profiling will not be activated.") + return + + directory = os.path.dirname(sqllogfilename) + if directory and not os.path.exists(directory): + raise FileNotFoundError(f"Directory {directory} does not exists ! ") + df = pandas.DataFrame([], columns=["Query", "Total Time [s.]"]) - df.to_csv(results_file, mode="a", header=True, index=None, sep=";") + df.to_csv(sqllogfilename, header=True, index=None, sep=";") - # @event.listens_for(Engine, "before_cursor_execute") def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): conn.info.setdefault("query_start_time", []).append(time.time()) logger.debug("Start Query: %s" % statement) @@ -36,7 +52,7 @@ def after_cursor_execute(conn, cursor, statement, parameters, context, executema logger.debug("Total Time: %f" % total) if statement.startswith("SELECT"): df = pandas.DataFrame([[statement, total]], columns=["Query", "Total Time"]) - df.to_csv(results_file, mode="a", header=False, index=None, sep=";") + df.to_csv(sqllogfilename, mode="a", header=False, index=None, sep=";") event.listen(db.engine, "before_cursor_execute", before_cursor_execute) event.listen(db.engine, "after_cursor_execute", after_cursor_execute) diff --git a/backend/geonature/tests/conftest.py b/backend/geonature/tests/conftest.py index ca9ffcb72b..da33a4ab5c 100644 --- a/backend/geonature/tests/conftest.py +++ b/backend/geonature/tests/conftest.py @@ -1,3 +1,13 @@ # force discovery of some fixtures from .fixtures import app, users, _session from pypnusershub.tests.fixtures import teardown_logout_user +import pytest + + +def pytest_addoption(parser): + parser.addoption("--sql-log-filename", action="store", default=None) + + +@pytest.fixture(scope="session") +def sqllogfilename(request): + return request.config.getoption("--sql-log-filename") From 55b78f84808f1429bb8211eb5d03d12f5a50b289 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 26 Mar 2024 16:52:08 +0100 Subject: [PATCH 26/27] (test,eval perf) enable sql log to all benchmark --- .../geonature/tests/benchmarks/test_benchmark_gn_meta.py | 3 ++- backend/geonature/tests/benchmarks/test_benchmark_occhab.py | 3 ++- backend/geonature/tests/benchmarks/test_benchmark_occtax.py | 4 +++- .../geonature/tests/benchmarks/test_benchmark_synthese.py | 6 ++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py b/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py index 51242bd4a2..aadf43f64c 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_gn_meta.py @@ -4,6 +4,7 @@ from geonature.tests.test_pr_occhab import stations from .benchmark_generator import BenchmarkTest, CLater +from .utils import activate_profiling_sql logging.basicConfig() logger = logging.getLogger("logger-name") @@ -13,7 +14,7 @@ @pytest.mark.benchmark(group="gn_meta") -@pytest.mark.usefixtures("client_class", "temporary_transaction") +@pytest.mark.usefixtures("client_class", "temporary_transaction", "activate_profiling_sql") class TestBenchmarkGnMeta: test_list_acquisition_frameworks = BenchmarkTest( diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py index 5b8e920233..0cee7edbd3 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occhab.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occhab.py @@ -4,6 +4,7 @@ from geonature.tests.test_pr_occhab import stations from .benchmark_generator import BenchmarkTest, CLater +from .utils import activate_profiling_sql logging.basicConfig() logger = logging.getLogger("logger-name") @@ -13,7 +14,7 @@ @pytest.mark.benchmark(group="occhab") -@pytest.mark.usefixtures("client_class", "temporary_transaction") +@pytest.mark.usefixtures("client_class", "temporary_transaction", "activate_profiling_sql") class TestBenchmarkOcchab: test_get_station = BenchmarkTest( diff --git a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py index a27f16e32e..092604b3ec 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_occtax.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_occtax.py @@ -5,6 +5,8 @@ from .benchmark_generator import BenchmarkTest, CLater +from .utils import activate_profiling_sql + logging.basicConfig() logger = logging.getLogger("logger-name") logger.setLevel(logging.DEBUG) @@ -13,7 +15,7 @@ @pytest.mark.benchmark(group="occtax") -@pytest.mark.usefixtures("client_class", "temporary_transaction") +@pytest.mark.usefixtures("client_class", "temporary_transaction", "activate_profiling_sql") class TestBenchmarkOcctax: test_list_releves_restricted = BenchmarkTest( diff --git a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py index 7aecce662d..c757dbff0c 100644 --- a/backend/geonature/tests/benchmarks/test_benchmark_synthese.py +++ b/backend/geonature/tests/benchmarks/test_benchmark_synthese.py @@ -4,6 +4,7 @@ from geonature.tests.benchmarks import * from geonature.tests.test_pr_occhab import stations from geonature.core.gn_synthese.models import Synthese +from .utils import activate_profiling_sql from .benchmark_generator import BenchmarkTest, CLater @@ -21,10 +22,7 @@ @pytest.mark.benchmark(group="synthese") -@pytest.mark.usefixtures( - "client_class", - "temporary_transaction", -) # , "activate_profiling_sql") +@pytest.mark.usefixtures("client_class", "temporary_transaction", "activate_profiling_sql") class TestBenchmarkSynthese: # GET NOMENCLATURE test_get_default_nomenclatures = BenchmarkTest( From e77cd83b7e9cb008b62c3eed3229c8b4414f37a0 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Wed, 27 Mar 2024 10:34:52 +0100 Subject: [PATCH 27/27] feat(eval-perf,sql log): now include endpoint --- .github/workflows/eval.yml | 6 +++--- backend/geonature/tests/benchmarks/utils.py | 14 +++++++++----- backend/geonature/tests/conftest.py | 2 ++ backend/geonature/tests/fixtures.py | 6 +++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml index 8c80e6fe4d..060cf8546b 100644 --- a/.github/workflows/eval.yml +++ b/.github/workflows/eval.yml @@ -4,7 +4,7 @@ on: jobs: build: - runs-on: self-hosted + runs-on: ubuntu-latest strategy: fail-fast: false @@ -12,11 +12,11 @@ jobs: debian-version: ['11', '12'] include: - debian-version: '11' - # python-version: "3.9" + python-version: '3.9' postgres-version: '13' postgis-version: '3.2' - debian-version: '12' - # python-version: "3.11" + python-version: '3.11' postgres-version: '15' postgis-version: '3.3' diff --git a/backend/geonature/tests/benchmarks/utils.py b/backend/geonature/tests/benchmarks/utils.py index fe33ccdafb..eeb049b54e 100644 --- a/backend/geonature/tests/benchmarks/utils.py +++ b/backend/geonature/tests/benchmarks/utils.py @@ -9,14 +9,16 @@ from geonature.utils.env import db from .benchmark_generator import CLater, BenchmarkTest from geonature.tests.test_synthese import blur_sensitive_observations +import traceback +from geonature.tests.fixtures import app logging.basicConfig() logger = logging.getLogger("logger-name") logger.setLevel(logging.DEBUG) -@pytest.fixture(scope="session") -def activate_profiling_sql(sqllogfilename: pytest.FixtureDef): +@pytest.fixture(scope="function") +def activate_profiling_sql(sqllogfilename: pytest.FixtureDef, app: pytest.FixtureDef): """ Fixture to activate profiling for SQL queries and store query's statements and execution times in a CSV file. @@ -29,6 +31,7 @@ def activate_profiling_sql(sqllogfilename: pytest.FixtureDef): The path to the CSV file where the query statements and execution times will be stored. """ + columns = ["Endpoint", "Query", "Total Time [s.]"] if not sqllogfilename: logger.debug("No SQL Log file provided. SQL Profiling will not be activated.") @@ -38,8 +41,9 @@ def activate_profiling_sql(sqllogfilename: pytest.FixtureDef): if directory and not os.path.exists(directory): raise FileNotFoundError(f"Directory {directory} does not exists ! ") - df = pandas.DataFrame([], columns=["Query", "Total Time [s.]"]) - df.to_csv(sqllogfilename, header=True, index=None, sep=";") + if not os.path.exists(sqllogfilename): + df = pandas.DataFrame([], columns=columns) + df.to_csv(sqllogfilename, header=True, index=None, sep=";") def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): conn.info.setdefault("query_start_time", []).append(time.time()) @@ -51,7 +55,7 @@ def after_cursor_execute(conn, cursor, statement, parameters, context, executema logger.debug("Query Complete!") logger.debug("Total Time: %f" % total) if statement.startswith("SELECT"): - df = pandas.DataFrame([[statement, total]], columns=["Query", "Total Time"]) + df = pandas.DataFrame([[pytest.endpoint, statement, total]], columns=columns) df.to_csv(sqllogfilename, mode="a", header=False, index=None, sep=";") event.listen(db.engine, "before_cursor_execute", before_cursor_execute) diff --git a/backend/geonature/tests/conftest.py b/backend/geonature/tests/conftest.py index da33a4ab5c..1b05115836 100644 --- a/backend/geonature/tests/conftest.py +++ b/backend/geonature/tests/conftest.py @@ -3,6 +3,8 @@ from pypnusershub.tests.fixtures import teardown_logout_user import pytest +pytest.endpoint = "" + def pytest_addoption(parser): parser.addoption("--sql-log-filename", action="store", default=None) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index 569b708732..bffb671cc4 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -7,7 +7,7 @@ import pytest import sqlalchemy as sa -from flask import current_app, testing, url_for +from flask import current_app, testing, url_for, request from geoalchemy2.shape import from_shape from PIL import Image from shapely.geometry import Point @@ -97,6 +97,10 @@ def app(): app.test_client_class = GeoNatureClient app.config["SERVER_NAME"] = "test.geonature.fr" # required by url_for + @app.before_request + def get_endpoint(): + pytest.endpoint = request.endpoint + with app.app_context(): """ Note: This may seem redundant with 'temporary_transaction' fixture.