diff --git a/CHANGELOG.md b/CHANGELOG.md
index 978cdfd9d8444d459ad2758a7bde099be58adce2..e491adba0dfcc65c6d4f7d07485dde3038cd7de2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
# CHANGELOG
-### Pre-release v4.0.0b3 - Release date: 2023-02-09
+### Pre-release v4.0.0b4 - Release date: 2024-02-23
+
+* The background task that updates the experiment status has been refactored. Now, it keeps the records of all the experiments
+* **Major change:** Removed `experiment_times` and `job_times` tables and background tasks related to them
+* Fixed bug when performance metrics are not calculated when there is only one SIM job
+* Multiple tests have been added
+* Testing module configuration fixtures have been fixed
+* A lot of dead code has been removed
+* Fix the decoding issue on graph coordinates generation on the background task
+
+### Pre-release v4.0.0b3 - Release date: 2024-02-09
* Fix HPC value in the running endpoint
* **Major change:** Updated all route names. Versioning path prefix is included:
diff --git a/README.md b/README.md
index dcc7389e88f7002e72e3c7eca833d7a7ad85c0c9..c33ed7c8b575e7401ed505c4ce3d7c4e7b395d34 100644
--- a/README.md
+++ b/README.md
@@ -70,19 +70,19 @@ The Autosubmit API have some configuration options that can be modified by setti
### Install pytest
```bash
-pip install -U pytest pytest-cov
+pip install -e .[test]
```
### Run tests:
```bash
-pytest tests/*
+pytest
```
### Run tests with coverage HTML report:
```bash
-pytest --cov=autosubmit_api --cov-config=.coveragerc --cov-report=html tests/*
+pytest --cov=autosubmit_api --cov-config=.coveragerc --cov-report=html tests/
```
You will find the report in `htmlcov/index.html`
diff --git a/autosubmit_api/__init__.py b/autosubmit_api/__init__.py
index 8226b2e173ba6816bd3a4ee2f31809d89c138746..31671cf47a0b214d028dfdb2bb1c04f2f7e50be1 100644
--- a/autosubmit_api/__init__.py
+++ b/autosubmit_api/__init__.py
@@ -17,6 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with Autosubmit. If not, see .
-__version__ = "4.0.0b3"
+__version__ = "4.0.0b4"
__author__ = "Luiggi Tenorio, Bruno P. Kinoshita, Cristian GutiƩrrez, Julian Berlin, Wilmer Uruchi"
__credits__ = "Barcelona Supercomputing Center"
\ No newline at end of file
diff --git a/autosubmit_api/app.py b/autosubmit_api/app.py
index 5c7acd71a9137ef5e6da9225375633b5d6f6b652..b191135239c77afa832a98abc81dc67fb9b0cbbd 100644
--- a/autosubmit_api/app.py
+++ b/autosubmit_api/app.py
@@ -6,7 +6,7 @@ from flask import Flask
from autosubmit_api.bgtasks.scheduler import create_bind_scheduler
from autosubmit_api.blueprints.v3 import create_v3_blueprint
from autosubmit_api.blueprints.v4 import create_v4_blueprint
-from autosubmit_api.database.extended_db import ExtendedDB
+from autosubmit_api.database import prepare_db
from autosubmit_api.experiment import common_requests as CommonRequests
from autosubmit_api.logger import get_app_logger
from autosubmit_api.config.basicConfig import APIBasicConfig
@@ -66,11 +66,8 @@ def create_app():
)
# Prepare DB
- ext_db = ExtendedDB(
- APIBasicConfig.DB_DIR, APIBasicConfig.DB_FILE, APIBasicConfig.AS_TIMES_DB
- )
- ext_db.prepare_db()
-
+ prepare_db()
+
# Background Scheduler
create_bind_scheduler(app)
diff --git a/autosubmit_api/auth/__init__.py b/autosubmit_api/auth/__init__.py
index b15562c61716c5f2a1c8d5dc3e010db8ff4c5e39..92ee07ab2b97200ac1eec43b0aae89fb6f85fe73 100644
--- a/autosubmit_api/auth/__init__.py
+++ b/autosubmit_api/auth/__init__.py
@@ -3,7 +3,7 @@ from http import HTTPStatus
from flask import request
import jwt
from autosubmit_api.logger import logger
-from autosubmit_api.config import PROTECTION_LEVEL, JWT_ALGORITHM, JWT_SECRET
+from autosubmit_api import config
from enum import IntEnum
@@ -45,12 +45,12 @@ def with_auth_token(
def inner_wrapper(*args, **kwargs):
try:
current_token = request.headers.get("Authorization")
- jwt_token = jwt.decode(current_token, JWT_SECRET, JWT_ALGORITHM)
+ jwt_token = jwt.decode(current_token, config.JWT_SECRET, config.JWT_ALGORITHM)
except Exception as exc:
error_msg = "Unauthorized"
if isinstance(exc, jwt.ExpiredSignatureError):
error_msg = "Expired token"
- auth_level = _parse_protection_level_env(PROTECTION_LEVEL)
+ auth_level = _parse_protection_level_env(config.PROTECTION_LEVEL)
if threshold <= auth_level: # If True, will trigger *_on_fail
if raise_on_fail:
raise AppAuthError(error_msg)
diff --git a/autosubmit_api/autosubmit_legacy/job/job_list.py b/autosubmit_api/autosubmit_legacy/job/job_list.py
index 40cff2ac604c1b9a4eacd85e44910183604d2bfe..c9ad0cba243cd707061de96e6ea092d122f1f8a6 100644
--- a/autosubmit_api/autosubmit_legacy/job/job_list.py
+++ b/autosubmit_api/autosubmit_legacy/job/job_list.py
@@ -31,6 +31,7 @@ from dateutil.relativedelta import *
from bscearth.utils.log import Log
from autosubmit_api.autosubmit_legacy.job.job_utils import SubJob
from autosubmit_api.autosubmit_legacy.job.job_utils import SubJobManager, job_times_to_text
+from autosubmit_api.config.basicConfig import APIBasicConfig
from autosubmit_api.performance.utils import calculate_ASYPD_perjob, calculate_SYPD_perjob
from autosubmit_api.components.jobs import utils as JUtils
from autosubmit_api.monitor.monitor import Monitor
@@ -46,6 +47,8 @@ from autosubmit_api.history.data_classes.job_data import JobData
from typing import List, Dict, Tuple
+from autosubmit_api.persistance.experiment import ExperimentPaths
+
class JobList:
"""
@@ -153,8 +156,8 @@ class JobList:
date_member_repetition = {}
job_name_to_job_title = {}
job_name_to_job = {job.job_name: job for job in job_list}
- path_to_logs = os.path.join(
- BasicConfig.LOCAL_ROOT_DIR, expid, "tmp", "LOG_" + expid)
+ exp_paths = ExperimentPaths(expid)
+ path_to_logs = exp_paths.tmp_log_dir
packages = {job.rowtype for job in job_list if job.rowtype > 2}
package_to_jobs = {package: [
@@ -577,7 +580,7 @@ class JobList:
return ""
@staticmethod
- def get_job_times_collection(basic_config, allJobs, expid, job_to_package=None, package_to_jobs=None, timeseconds=True):
+ def get_job_times_collection(basic_config: APIBasicConfig, allJobs, expid, job_to_package=None, package_to_jobs=None, timeseconds=True):
"""
Gets queuing and running time for the collection of jobs
@@ -590,7 +593,7 @@ class JobList:
conn = DbRequests.create_connection(db_file)
# job_data = None
# Job information from worker database
- job_times = DbRequests.get_times_detail_by_expid(conn, expid)
+ # job_times = dict() # REMOVED: DbRequests.get_times_detail_by_expid(conn, expid)
conn.close()
# Job information from job historic data
# print("Get current job data structure...")
@@ -612,7 +615,7 @@ class JobList:
# print("Start main loop")
for job in allJobs:
job_info = JobList.retrieve_times(
- job.status, job.name, job._tmp_path, make_exception=False, job_times=job_times, seconds=timeseconds, job_data_collection=job_data)
+ job.status, job.name, job._tmp_path, make_exception=False, job_times=None, seconds=timeseconds, job_data_collection=job_data)
# if job_info:
job_name_to_job_info[job.name] = job_info
time_total = (job_info.queue_time +
@@ -860,23 +863,21 @@ class JobList:
"""
monitor = Monitor()
packages = None
+ exp_paths = ExperimentPaths(expid)
try:
- packages = JobPackagePersistence(os.path.join(basic_config.LOCAL_ROOT_DIR, expid, "pkl"),
- "job_packages_" + expid).load(wrapper=False)
+ packages = JobPackagePersistence(exp_paths.job_packages_db).load(wrapper=False)
# if the main table exist but is empty, we try the other one
if not (any(packages.keys()) or any(packages.values())):
Log.info("Wrapper table empty, trying packages.")
- packages = JobPackagePersistence(os.path.join(basic_config.LOCAL_ROOT_DIR, expid, "pkl"),
- "job_packages_" + expid).load(wrapper=True)
+ packages = JobPackagePersistence(exp_paths.job_packages_db).load(wrapper=True)
except Exception as ex:
print("Wrapper table not found, trying packages.")
packages = None
try:
- packages = JobPackagePersistence(os.path.join(basic_config.LOCAL_ROOT_DIR, expid, "pkl"),
- "job_packages_" + expid).load(wrapper=True)
+ packages = JobPackagePersistence(exp_paths.job_packages_db).load(wrapper=True)
except Exception as exp2:
packages = None
pass
diff --git a/autosubmit_api/bgtasks/bgtask.py b/autosubmit_api/bgtasks/bgtask.py
index 30698b62d6db6ec7dc36f4ec10da18a9a9ca31b7..a23aa27ec76b64f0928ba408a96005f7fda34dcd 100644
--- a/autosubmit_api/bgtasks/bgtask.py
+++ b/autosubmit_api/bgtasks/bgtask.py
@@ -1,91 +1,63 @@
from abc import ABC, abstractmethod
-from autosubmit_api.experiment import common_requests
-from autosubmit_api.history.experiment_status_manager import ExperimentStatusManager
+import traceback
+from autosubmit_api.logger import logger
from autosubmit_api.config.basicConfig import APIBasicConfig
-from autosubmit_api.workers.business import populate_times, process_graph_drawings
+from autosubmit_api.workers.business import process_graph_drawings
from autosubmit_api.workers.populate_details.populate import DetailsProcessor
-class BackgroundTask(ABC):
- @staticmethod
+class BackgroundTaskTemplate(ABC):
+ """
+ Interface to define Background Tasks.
+ Do not override the run method.
+ """
+
+ logger = logger
+
+ @classmethod
+ def run(cls):
+ """
+ Not blocking exceptions
+ """
+ try:
+ cls.procedure()
+ except Exception as exc:
+ cls.logger.error(f"Exception on Background Task {cls.id}: {exc}")
+ cls.logger.error(traceback.print_exc())
+
+ @classmethod
@abstractmethod
- def run():
+ def procedure(cls):
raise NotImplementedError
-
+
@property
@abstractmethod
def id(self) -> dict:
raise NotImplementedError
-
+
@property
@abstractmethod
def trigger_options(self) -> dict:
raise NotImplementedError
-
-class PopulateDetailsDB(BackgroundTask):
+
+class PopulateDetailsDB(BackgroundTaskTemplate):
id = "TASK_POPDET"
- trigger_options = {
- "trigger": "interval",
- "hours": 4
- }
+ trigger_options = {"trigger": "interval", "hours": 4}
- @staticmethod
- def run():
+ @classmethod
+ def procedure(cls):
APIBasicConfig.read()
return DetailsProcessor(APIBasicConfig).process()
-
-
-class PopulateQueueRuntimes(BackgroundTask):
- id = "TASK_POPQUE"
- trigger_options = {
- "trigger": "interval",
- "minutes": 3
- }
-
- @staticmethod
- def run():
- """ Process and updates queuing and running times. """
- populate_times.process_completed_times()
-
-
-class PopulateRunningExperiments(BackgroundTask):
- id = "TASK_POPRUNEX"
- trigger_options = {
- "trigger": "interval",
- "minutes": 5
- }
-
- @staticmethod
- def run():
- """
- Updates STATUS of experiments.
- """
- ExperimentStatusManager().update_running_experiments()
-
-
-class VerifyComplete(BackgroundTask):
- id = "TASK_VRFCMPT"
- trigger_options = {
- "trigger": "interval",
- "minutes": 10
- }
- @staticmethod
- def run():
- common_requests.verify_last_completed(1800)
-
-class PopulateGraph(BackgroundTask):
+class PopulateGraph(BackgroundTaskTemplate):
id = "TASK_POPGRPH"
- trigger_options = {
- "trigger": "interval",
- "hours": 24
- }
+ trigger_options = {"trigger": "interval", "hours": 24}
- @staticmethod
- def run():
+ @classmethod
+ def procedure(cls):
"""
Process coordinates of nodes in a graph drawing and saves them.
"""
- process_graph_drawings.process_active_graphs()
\ No newline at end of file
+ process_graph_drawings.process_active_graphs()
diff --git a/autosubmit_api/bgtasks/scheduler.py b/autosubmit_api/bgtasks/scheduler.py
index 2c667c1cbb5f78cc4edb4d6fd04ab8cf8ef0d323..6e890a6453211040c0f78a48b0ddb6c86d3b1ca1 100644
--- a/autosubmit_api/bgtasks/scheduler.py
+++ b/autosubmit_api/bgtasks/scheduler.py
@@ -1,13 +1,11 @@
from typing import List
from flask_apscheduler import APScheduler
from autosubmit_api.bgtasks.bgtask import (
- BackgroundTask,
+ BackgroundTaskTemplate,
PopulateDetailsDB,
- PopulateQueueRuntimes,
- PopulateRunningExperiments,
- VerifyComplete,
PopulateGraph,
)
+from autosubmit_api.bgtasks.tasks.status_updater import StatusUpdater
from autosubmit_api.config import (
DISABLE_BACKGROUND_TASKS,
RUN_BACKGROUND_TASKS_ON_START,
@@ -15,11 +13,9 @@ from autosubmit_api.config import (
from autosubmit_api.logger import logger, with_log_run_times
-REGISTERED_TASKS: List[BackgroundTask] = [
+REGISTERED_TASKS: List[BackgroundTaskTemplate] = [
PopulateDetailsDB,
- PopulateQueueRuntimes,
- PopulateRunningExperiments,
- VerifyComplete,
+ StatusUpdater,
PopulateGraph,
]
diff --git a/autosubmit_api/history/platform_monitor/__init__.py b/autosubmit_api/bgtasks/tasks/__init__.py
similarity index 100%
rename from autosubmit_api/history/platform_monitor/__init__.py
rename to autosubmit_api/bgtasks/tasks/__init__.py
diff --git a/autosubmit_api/bgtasks/tasks/status_updater.py b/autosubmit_api/bgtasks/tasks/status_updater.py
new file mode 100644
index 0000000000000000000000000000000000000000..186be6fccb18c783a2c3c9079d9389a66b3b23c8
--- /dev/null
+++ b/autosubmit_api/bgtasks/tasks/status_updater.py
@@ -0,0 +1,119 @@
+from datetime import datetime
+import os
+import time
+from typing import List
+
+from sqlalchemy import select
+from autosubmit_api.bgtasks.bgtask import BackgroundTaskTemplate
+from autosubmit_api.database import tables
+from autosubmit_api.database.common import (
+ create_autosubmit_db_engine,
+ create_as_times_db_engine,
+ create_main_db_conn,
+)
+from autosubmit_api.database.models import ExperimentModel
+from autosubmit_api.experiment.common_requests import _is_exp_running
+from autosubmit_api.history.database_managers.database_models import RunningStatus
+from autosubmit_api.persistance.experiment import ExperimentPaths
+
+
+class StatusUpdater(BackgroundTaskTemplate):
+ id = "TASK_STTSUPDTR"
+ trigger_options = {"trigger": "interval", "minutes": 5}
+
+ @classmethod
+ def _clear_missing_experiments(cls):
+ """
+ Clears the experiments that are not in the experiments table
+ """
+ with create_main_db_conn() as conn:
+ try:
+ del_stmnt = tables.experiment_status_table.delete().where(
+ tables.experiment_status_table.c.exp_id.not_in(
+ select(tables.experiment_table.c.id)
+ )
+ )
+ conn.execute(del_stmnt)
+ conn.commit()
+ except Exception as exc:
+ conn.rollback()
+ cls.logger.error(
+ f"[{cls.id}] Error while clearing missing experiments status: {exc}"
+ )
+
+ @classmethod
+ def _get_experiments(cls) -> List[ExperimentModel]:
+ """
+ Get the experiments list
+ """
+ with create_autosubmit_db_engine().connect() as conn:
+ query_result = conn.execute(tables.experiment_table.select()).all()
+ return [ExperimentModel.model_validate(row._mapping) for row in query_result]
+
+ @classmethod
+ def _check_exp_running(cls, expid: str) -> bool:
+ """
+ Decide if the experiment is running
+ """
+ MAX_PKL_AGE = 600 # 10 minutes
+ MAX_PKL_AGE_EXHAUSTIVE = 3600 # 1 hour
+
+ is_running = False
+ try:
+ pkl_path = ExperimentPaths(expid).job_list_pkl
+ pkl_age = int(time.time()) - int(os.stat(pkl_path).st_mtime)
+
+ if pkl_age < MAX_PKL_AGE: # First running check
+ is_running = True
+ elif pkl_age < MAX_PKL_AGE_EXHAUSTIVE: # Exhaustive check
+ _, _, _flag, _, _ = _is_exp_running(expid) # Exhaustive validation
+ if _flag:
+ is_running = True
+ except Exception as exc:
+ cls.logger.error(
+ f"[{cls.id}] Error while checking experiment {expid}: {exc}"
+ )
+
+ return is_running
+
+ @classmethod
+ def _update_experiment_status(cls, experiment: ExperimentModel, is_running: bool):
+ with create_as_times_db_engine().connect() as conn:
+ try:
+ del_stmnt = tables.experiment_status_table.delete().where(
+ tables.experiment_status_table.c.exp_id == experiment.id
+ )
+ ins_stmnt = tables.experiment_status_table.insert().values(
+ exp_id=experiment.id,
+ name=experiment.name,
+ status=(
+ RunningStatus.RUNNING
+ if is_running
+ else RunningStatus.NOT_RUNNING
+ ),
+ seconds_diff=0,
+ modified=datetime.now().isoformat(sep="-", timespec="seconds"),
+ )
+ conn.execute(del_stmnt)
+ conn.execute(ins_stmnt)
+ conn.commit()
+ except Exception as exc:
+ conn.rollback()
+ cls.logger.error(
+ f"[{cls.id}] Error while doing database operations on experiment {experiment.name}: {exc}"
+ )
+
+ @classmethod
+ def procedure(cls):
+ """
+ Updates STATUS of experiments.
+ """
+ cls._clear_missing_experiments()
+
+ # Read experiments table
+ exp_list = cls._get_experiments()
+
+ # Check every experiment status & update
+ for experiment in exp_list:
+ is_running = cls._check_exp_running(experiment.name)
+ cls._update_experiment_status(experiment, is_running)
diff --git a/autosubmit_api/builders/experiment_builder.py b/autosubmit_api/builders/experiment_builder.py
index 5a3b5a60afab4de3c8302778356b22e1ede99c8b..7bf5c48d17810eb61f78ed6285ea3c8147d86690 100644
--- a/autosubmit_api/builders/experiment_builder.py
+++ b/autosubmit_api/builders/experiment_builder.py
@@ -4,16 +4,16 @@ from autosubmit_api.builders.configuration_facade_builder import (
ConfigurationFacadeDirector,
)
from autosubmit_api.database import tables
-from autosubmit_api.database.common import create_main_db_conn
-from autosubmit_api.database.models import Experiment
+from autosubmit_api.database.common import create_autosubmit_db_engine, create_main_db_conn
+from autosubmit_api.database.models import ExperimentModel
class ExperimentBuilder(BaseBuilder):
def produce_base_from_dict(self, obj: dict):
- self._product = Experiment.model_validate(obj)
+ self._product = ExperimentModel.model_validate(obj)
def produce_base(self, expid):
- with create_main_db_conn() as conn:
+ with create_autosubmit_db_engine().connect() as conn:
result = conn.execute(
tables.experiment_table.select().where(
tables.experiment_table.c.name == expid
@@ -21,7 +21,7 @@ class ExperimentBuilder(BaseBuilder):
).one()
# Set new product
- self._product = Experiment(
+ self._product = ExperimentModel(
id=result.id,
name=result.name,
description=result.description,
@@ -30,12 +30,12 @@ class ExperimentBuilder(BaseBuilder):
def produce_details(self):
exp_id = self._product.id
- with create_main_db_conn() as conn:
+ with create_autosubmit_db_engine().connect()() as conn:
result = conn.execute(
tables.details_table.select().where(
tables.details_table.c.exp_id == exp_id
)
- ).one_or_none
+ ).one_or_none()
# Set details props
if result:
@@ -63,5 +63,5 @@ class ExperimentBuilder(BaseBuilder):
)
@property
- def product(self) -> Experiment:
+ def product(self) -> ExperimentModel:
return super().product
\ No newline at end of file
diff --git a/autosubmit_api/common/utils.py b/autosubmit_api/common/utils.py
index 7056076fa7b6727dcd3c0d2d1045c57e0b18de4f..e0ce68f253f0f482ecd138d4daa85d49dca49b71 100644
--- a/autosubmit_api/common/utils.py
+++ b/autosubmit_api/common/utils.py
@@ -71,12 +71,12 @@ def parse_number_processors(processors_str):
except:
return 1
-def get_jobs_with_no_outliers(jobs):
+def get_jobs_with_no_outliers(jobs: List):
""" Detects outliers and removes them from the returned list """
new_list = []
data_run_times = [job.run_time for job in jobs]
# print(data_run_times)
- if len(data_run_times) == 0:
+ if len(data_run_times) <= 1:
return jobs
mean = statistics.mean(data_run_times)
diff --git a/autosubmit_api/components/experiment/configuration_facade.py b/autosubmit_api/components/experiment/configuration_facade.py
index 47762a2432313781d542a2244fb7aac884141cea..8ff5fade857778cc1473b46c5c1f03ef70d57587 100644
--- a/autosubmit_api/components/experiment/configuration_facade.py
+++ b/autosubmit_api/components/experiment/configuration_facade.py
@@ -11,6 +11,8 @@ from abc import ABCMeta, abstractmethod
from autosubmit_api.common.utils import JobSection, parse_number_processors, timestamp_to_datetime_format, datechunk_to_year
from typing import List
+from autosubmit_api.persistance.experiment import ExperimentPaths
+
class ProjectType:
GIT = "git"
SVN = "svn"
@@ -40,11 +42,12 @@ class ConfigurationFacade(metaclass=ABCMeta):
def _process_basic_config(self):
# type: () -> None
- self.pkl_filename = "job_list_{0}.pkl".format(self.expid)
- self.experiment_path = os.path.join(self.basic_configuration.LOCAL_ROOT_DIR, self.expid)
- self.pkl_path = os.path.join(self.basic_configuration.LOCAL_ROOT_DIR, self.expid, "pkl", self.pkl_filename)
- self.tmp_path = os.path.join(self.basic_configuration.LOCAL_ROOT_DIR, self.expid, self.basic_configuration.LOCAL_TMP_DIR)
- self.log_path = os.path.join(self.basic_configuration.LOCAL_ROOT_DIR, self.expid, "tmp", "LOG_{0}".format(self.expid))
+ exp_paths = ExperimentPaths(self.expid)
+ self.pkl_filename = os.path.basename(exp_paths.job_list_pkl)
+ self.experiment_path = exp_paths.exp_dir
+ self.pkl_path = exp_paths.job_list_pkl
+ self.tmp_path = exp_paths.tmp_dir
+ self.log_path = exp_paths.tmp_log_dir
self.structures_path = self.basic_configuration.STRUCTURES_DIR
if not os.path.exists(self.experiment_path): raise IOError("Experiment folder {0} not found".format(self.experiment_path))
if not os.path.exists(self.pkl_path): raise IOError("Required file {0} not found.".format(self.pkl_path))
diff --git a/autosubmit_api/components/jobs/test.py b/autosubmit_api/components/jobs/test.py
deleted file mode 100644
index dc64a7531ff11c6e2103d15abf4a7694af09badc..0000000000000000000000000000000000000000
--- a/autosubmit_api/components/jobs/test.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-
-import unittest
-from components.jobs.joblist_loader import JobListLoader
-
-
-class TestJobListHelper(unittest.TestCase):
- def setUp(self):
- pass
-
- def get_joblist_helper(self):
- pass
-
-class TestJobListLoader(unittest.TestCase):
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def test_load(self):
- pass
- # loader = JobListLoader("a29z")
- # loader.load_jobs()
- # self.assertTrue(len(loader.jobs) > 0)
- # for job in loader.jobs:
- # job.do_print()
-
- # def test_loader(self):
- # tree =
-
- # def test_load_out_err_files(self):
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/autosubmit_api/config/basicConfig.py b/autosubmit_api/config/basicConfig.py
index fdb9e57013a7bc2281effaed95112a221afd9a07..95acbff22e737fec77104603dd8bf939fa80bbdf 100644
--- a/autosubmit_api/config/basicConfig.py
+++ b/autosubmit_api/config/basicConfig.py
@@ -27,8 +27,8 @@ class APIBasicConfig(BasicConfig):
Extended class to manage configuration for Autosubmit path, database and default values for new experiments in the Autosubmit API
"""
- GRAPHDATA_DIR = os.path.join(os.path.expanduser('~'), 'autosubmit', 'as_metadata', 'graph')
- FILE_STATUS_DIR = os.path.join(os.path.expanduser('~'), 'autosubmit', 'as_metadata', 'test')
+ GRAPHDATA_DIR = os.path.join(os.path.expanduser('~'), 'autosubmit', 'metadata', 'graph')
+ FILE_STATUS_DIR = os.path.join(os.path.expanduser('~'), 'autosubmit', 'metadata', 'test')
FILE_STATUS_DB = 'status.db'
ALLOWED_CLIENTS = set([])
@@ -45,6 +45,8 @@ class APIBasicConfig(BasicConfig):
if parser.has_option('graph', 'path'):
APIBasicConfig.GRAPHDATA_DIR = parser.get('graph', 'path')
+ else:
+ APIBasicConfig.GRAPHDATA_DIR = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, 'metadata', 'graph')
if parser.has_option('statusdb', 'path'):
APIBasicConfig.FILE_STATUS_DIR = parser.get('statusdb', 'path')
if parser.has_option('statusdb', 'filename'):
diff --git a/autosubmit_api/config/confConfigStrategy.py b/autosubmit_api/config/confConfigStrategy.py
index 9ccd8ee22b786fde5627ac5de3e8f5ebaa786e30..2bbadb2e438f81b17188e2dc353f030023dc039c 100644
--- a/autosubmit_api/config/confConfigStrategy.py
+++ b/autosubmit_api/config/confConfigStrategy.py
@@ -647,7 +647,7 @@ class confConfigStrategy(IConfigStrategy):
if self._proj_parser_file == '':
self._proj_parser = None
else:
- self._proj_parser = AutosubmitConfig.get_parser(
+ self._proj_parser = confConfigStrategy.get_parser(
self.parser_factory, self._proj_parser_file)
return True
except Exception as e:
diff --git a/autosubmit_api/config/files/autosubmit.conf b/autosubmit_api/config/files/autosubmit.conf
deleted file mode 100644
index 90770b8d0a9611157495ea14d9dfa8518faae208..0000000000000000000000000000000000000000
--- a/autosubmit_api/config/files/autosubmit.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-[config]
-# Experiment identifier
-# No need to change
-EXPID =
-# No need to change.
-# Autosubmit version identifier
-AUTOSUBMIT_VERSION =
-# Default maximum number of jobs to be waiting in any platform
-# Default = 3
-MAXWAITINGJOBS = 3
-# Default maximum number of jobs to be running at the same time at any platform
-# Default = 6
-TOTALJOBS = 6
-# Time (seconds) between connections to the HPC queue scheduler to poll already submitted jobs status
-# Default = 10
-SAFETYSLEEPTIME = 10
-# Number of retrials if a job fails. Can ve override at job level
-# Default = 0
-RETRIALS = 0
-
-[mail]
-# Enable mail notifications
-# Default = False
-NOTIFICATIONS = False
-# Mail address where notifications will be received
-TO =
-
-[communications]
-# Communications library used to connect with platforms: paramiko or saga.
-# Default = paramiko
-API = paramiko
-
-[storage]
-# Defines the way of storing the progress of the experiment. The available options are:
-# A PICKLE file (pkl) or an SQLite database (db). Default = pkl
-TYPE = pkl
-# Defines if the remote logs will be copied to the local platform. Default = True.
-COPY_REMOTE_LOGS = True
-
-[migrate]
-# Changes experiment files owner.
-TO_USER =
diff --git a/autosubmit_api/config/files/expdef.conf b/autosubmit_api/config/files/expdef.conf
deleted file mode 100644
index 96e2b6fd32c5c2e5153fba6c82367f2677f7210f..0000000000000000000000000000000000000000
--- a/autosubmit_api/config/files/expdef.conf
+++ /dev/null
@@ -1,77 +0,0 @@
-[DEFAULT]
-# Experiment identifier
-# No need to change
-EXPID =
-# HPC name.
-# No need to change
-HPCARCH =
-
-[experiment]
-# Supply the list of start dates. Available formats: YYYYMMDD YYYYMMDDhh YYYYMMDDhhmm
-# You can also use an abbreviated syntax for multiple dates with common parts: 200001[01 15] <=> 20000101 20000115
-# 200001[01-04] <=> 20000101 20000102 20000103 20000104
-# DATELIST = 19600101 19650101 19700101
-# DATELIST = 1960[0101 0201 0301]
-# DATELIST = 19[60-65]
-DATELIST =
-# Supply the list of members. Format fcX
-# You can also use an abreviated syntax for multiple members: fc[0 1 2] <=> fc0 fc1 fc2
-# fc[0-2] <=> fc0 fc1 fc2
-# MEMBERS = fc0 fc1 fc2 fc3 fc4
-# MEMBERS = fc[0-4]
-MEMBERS =
-# Chunk size unit. STRING = hour, day, month, year
-CHUNKSIZEUNIT = month
-# Chunk size. NUMERIC = 4, 6, 12
-CHUNKSIZE =
-# Total number of chunks in experiment. NUMERIC = 30, 15, 10
-NUMCHUNKS =
-# Initial chunk of the experiment. Optional. DEFAULT = 1
-CHUNKINI =
-# Calendar used. LIST: standard, noleap
-CALENDAR = standard
-
-[project]
-# Select project type. STRING = git, svn, local, none
-# If PROJECT_TYPE is set to none, Autosubmit self-contained dummy templates will be used
-PROJECT_TYPE =
-# Destination folder name for project. type = STRING, default = leave empty,
-PROJECT_DESTINATION =
-
-# If PROJECT_TYPE is not git, no need to change
-[git]
-# Repository URL STRING = 'https://github.com/torvalds/linux.git'
-PROJECT_ORIGIN =
-# Select branch or tag, STRING, default = 'master', help = {'master' (default), 'develop', 'v3.1b', ...}
-PROJECT_BRANCH =
-# type = STRING, default = leave empty, help = if model branch is a TAG leave empty
-PROJECT_COMMIT =
-# type = STRING, default = leave empty and will load all submodules, help = loadThisSubmodule alsoloadthis anotherLoad ...
-PROJECT_SUBMODULES =
-# If PROJECT_TYPE is not svn, no need to change
-[svn]
-# type = STRING, help = 'https://svn.ec-earth.org/ecearth3'
-PROJECT_URL =
-# Select revision number. NUMERIC = 1778
-PROJECT_REVISION =
-
-# If PROJECT_TYPE is not local, no need to change
-[local]
-# type = STRING, help = /foo/bar/ecearth
-PROJECT_PATH =
-
-# If PROJECT_TYPE is none, no need to change
-[project_files]
-# Where is PROJECT CONFIGURATION file location relative to project root path
-FILE_PROJECT_CONF =
-# Where is JOBS CONFIGURATION file location relative to project root path
-FILE_JOBS_CONF =
-# Default job scripts type in the project. type = STRING, default = bash, supported = 'bash', 'python' or 'r'
-JOB_SCRIPTS_TYPE =
-
-[rerun]
-# Is a rerun or not? [Default: Do set FALSE]. BOOLEAN = TRUE, FALSE
-RERUN = FALSE
-# If RERUN = TRUE then supply the list of chunks to rerun
-# LIST = [ 19601101 [ fc0 [1 2 3 4] fc1 [1] ] 19651101 [ fc0 [16-30] ] ]
-CHUNKLIST =
\ No newline at end of file
diff --git a/autosubmit_api/config/files/jobs.conf b/autosubmit_api/config/files/jobs.conf
deleted file mode 100644
index eaf192ce6b081598bb98537b9079061013f74d8f..0000000000000000000000000000000000000000
--- a/autosubmit_api/config/files/jobs.conf
+++ /dev/null
@@ -1,96 +0,0 @@
-# Example job with all options specified
-
-## Job name
-# [JOBNAME]
-## Script to execute. If not specified, job will be omitted from workflow.
-## Path relative to the project directory
-# FILE =
-## Platform to execute the job. If not specified, defaults to HPCARCH in expdef file.
-## LOCAL is always defined and represents the current machine
-# PLATFORM =
-## Queue to add the job to. If not specified, uses PLATFORM default.
-# QUEUE =
-## Defines dependencies from job as a list of parents jobs separated by spaces.
-## Dependencies to jobs in previous chunk, member o startdate, use -(DISTANCE)
-# DEPENDENCIES = INI SIM-1 CLEAN-2
-## Define if jobs runs once, once per stardate, once per member or once per chunk. Options: once, date, member, chunk.
-## If not specified, defaults to once
-# RUNNING = once
-## Specifies that job has only to be run after X dates, members or chunk. A job will always be created for the last
-## If not specified, defaults to 1
-# FREQUENCY = 3
-## Specifies if a job with FREQUENCY > 1 has only to wait for all the jobs in the previous chunks on its period or just
-## for the ones in the chunk it is going to execute
-## If not specified, defaults to True
-# WAIT = False
-## Defines if job is only to be executed in reruns. If not specified, defaults to false.
-# RERUN_ONLY = False
-## Defines jobs needed to be rerun if this job is going to be rerunned
-# RERUN_DEPENDENCIES = RERUN INI LOCAL_SETUP REMOTE_SETUP TRANSFER
-## Wallclock to be submitted to the HPC queue in format HH:MM. If not specified, defaults to empty.
-# WALLCLOCK = 00:05
-## Processors number to be submitted to the HPC. If not specified, defaults to 1.
-# PROCESSORS = 1
-## Threads number to be submitted to the HPC. If not specified, defaults to 1.
-# THREADS = 1
-## Tasks number (number of processes per node) to be submitted to the HPC. If not specified, defaults to empty.
-# TASKS = 16
-## Memory requirements for the job in MB. Optional. If not specified, then not defined for the scheduler.
-# MEMORY = 4096
-## Memory per task requirements for the job in MB. Optional. If not specified, then not defined for the scheduler.
-# MEMORY_PER_TASK = 1024
-## Scratch free space requirements for the job in percentage (%). If not specified, it won't be defined on the template.
-# SCRATCH_FREE_SPACE = 10
-## Number of retrials if a job fails. If not specified, defaults to the value given on experiment's autosubmit.conf
-# RETRIALS = 4
-## Some jobs can not be checked before running previous jobs. Set this option to false if that is the case
-# CHECK = False
-## Select the interpreter that will run the job. Options: bash, python, r Default: bash
-# TYPE = bash
-## Synchronize a chunk job with its dependency chunks at a 'date' or 'member' level
-# SYNCHRONIZE = date | member
-## Optional. Custom directives for the resource manager of the platform used for that job.
-## Put as many as you wish in json formatted array.
-# CUSTOM_DIRECTIVE = ["#PBS -v myvar=value, "#PBS -v othervar=value"]
-
-[LOCAL_SETUP]
-FILE = LOCAL_SETUP.sh
-PLATFORM = LOCAL
-
-[REMOTE_SETUP]
-FILE = REMOTE_SETUP.sh
-DEPENDENCIES = LOCAL_SETUP
-WALLCLOCK = 00:05
-
-[INI]
-FILE = INI.sh
-DEPENDENCIES = REMOTE_SETUP
-RUNNING = member
-WALLCLOCK = 00:05
-
-[SIM]
-FILE = SIM.sh
-DEPENDENCIES = INI SIM-1 CLEAN-2
-RUNNING = chunk
-WALLCLOCK = 00:05
-PROCESSORS = 2
-THREADS = 1
-TASKS = 1
-
-[POST]
-FILE = POST.sh
-DEPENDENCIES = SIM
-RUNNING = chunk
-WALLCLOCK = 00:05
-
-[CLEAN]
-FILE = CLEAN.sh
-DEPENDENCIES = POST
-RUNNING = chunk
-WALLCLOCK = 00:05
-
-[TRANSFER]
-FILE = TRANSFER.sh
-PLATFORM = LOCAL
-DEPENDENCIES = CLEAN
-RUNNING = member
diff --git a/autosubmit_api/config/files/platforms.conf b/autosubmit_api/config/files/platforms.conf
deleted file mode 100644
index a06f59dd0c0a234b8027d5fe180590487bdb8227..0000000000000000000000000000000000000000
--- a/autosubmit_api/config/files/platforms.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-# Example platform with all options specified
-
-## Platform name
-# [PLATFORM]
-## Queue type. Options: PBS, SGE, PS, LSF, ecaccess, SLURM. Required
-# TYPE =
-## Version of queue manager to use. Needed only in PBS (options: 10, 11, 12) and ecaccess (options: pbs, loadleveler)
-# VERSION =
-## Hostname of the HPC. Required
-# HOST =
-## Project for the machine scheduler. Required
-# PROJECT =
-## Budget account for the machine scheduler. If omitted, takes the value defined in PROJECT
-# BUDGET =
-## Option to add project name to host. This is required for some HPCs.
-# ADD_PROJECT_TO_HOST = False
-## User for the machine scheduler. Required
-# USER =
-## Optional. If given, Autosubmit will change owner of files in given platform when using migrate_exp.
-# USER_TO =
-## Path to the scratch directory for the machine. Required.
-# SCRATCH_DIR = /scratch
-## Path to the machine's temporary directory for migrate purposes.
-# TEMP_DIR = /tmp
-## If true, Autosubmit test command can use this queue as a main queue. Defaults to False
-# TEST_SUITE = False
-## If given, Autosubmit will add jobs to the given queue. Required for some platforms.
-# QUEUE =
-## Optional. If given, Autosubmit will submit the serial jobs with the exclusivity directive.
-# EXCLUSIVITY =
-## Optional. If specified, autosubmit will run jobs with only one processor in the specified platform.
-# SERIAL_PLATFORM = SERIAL_PLATFORM_NAME
-## Optional. If specified, autosubmit will run jobs with only one processor in the specified queue.
-## Autosubmit will ignore this configuration if SERIAL_PLATFORM is provided
-# SERIAL_QUEUE = SERIAL_QUEUE_NAME
-## Optional. Default number of processors per node to be used in jobs
-# PROCESSORS_PER_NODE =
-## Optional. Integer. Scratch free space requirements for the platform in percentage (%).
-## If not specified, it won't be defined on the template.
-# SCRATCH_FREE_SPACE =
-## Optional. Integer. Default Maximum number of jobs to be waiting in any platform queue
-## Default = 3
-# MAX_WAITING_JOBS =
-## Optional. Integer. Default maximum number of jobs to be running at the same time at any platform
-## Default = 6
-# TOTAL_JOBS =
-## Max wallclock per job submitted to the HPC queue in format HH:MM. If not specified, defaults to empty.
-## Optional. Required for wrappers.
-# MAX_WALLCLOCK = 72:00
-## Max processors number per job submitted to the HPC. If not specified, defaults to empty.
-## Optional. Required for wrappers.
-# MAX_PROCESSORS = 1
-## Optional. Custom directives for the resource manager of the platform used.
-## Put as many as you wish in a json formatted array.
-# CUSTOM_DIRECTIVE = ["#PBS -v myvar=value, "#PBS -v othervar=value"]
diff --git a/autosubmit_api/database/__init__.py b/autosubmit_api/database/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e2c02e22ffbd99e3569df2bf6ffeae33f39325d4 100644
--- a/autosubmit_api/database/__init__.py
+++ b/autosubmit_api/database/__init__.py
@@ -0,0 +1,19 @@
+from sqlalchemy import text
+from autosubmit_api.database.common import (
+ create_as_times_db_engine,
+ create_autosubmit_db_engine,
+)
+from autosubmit_api.database.tables import experiment_status_table, details_table
+
+
+def prepare_db():
+ with create_as_times_db_engine().connect() as conn:
+ experiment_status_table.create(conn, checkfirst=True)
+
+ with create_autosubmit_db_engine().connect() as conn:
+ details_table.create(conn, checkfirst=True)
+
+ view_name = "listexp"
+ view_from = "select id,name,user,created,model,branch,hpc,description from experiment left join details on experiment.id = details.exp_id"
+ new_view_stmnt = f"CREATE VIEW IF NOT EXISTS {view_name} as {view_from}"
+ conn.execute(text(new_view_stmnt))
diff --git a/autosubmit_api/database/common.py b/autosubmit_api/database/common.py
index c39b76ddb957058c931177958edf764a2707a16f..6891df5403feed41ba4a06ac72b50a3cd10274a7 100644
--- a/autosubmit_api/database/common.py
+++ b/autosubmit_api/database/common.py
@@ -1,27 +1,63 @@
import os
from typing import Any
-from sqlalchemy import Connection, Select, create_engine, select, text, func
+from sqlalchemy import Connection, Engine, Select, create_engine, select, text, func
+from autosubmit_api.builders import BaseBuilder
from autosubmit_api.logger import logger
from autosubmit_api.config.basicConfig import APIBasicConfig
-def create_main_db_conn():
- APIBasicConfig.read()
- autosubmit_db_path = os.path.abspath(APIBasicConfig.DB_PATH)
- as_times_db_path = os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB)
- engine = create_engine("sqlite://")
- conn = engine.connect()
- conn.execute(text(f"attach database '{autosubmit_db_path}' as autosubmit;"))
- conn.execute(text(f"attach database '{as_times_db_path}' as as_times;"))
- return conn
+class AttachedDatabaseConnBuilder(BaseBuilder):
+ """
+ SQLite utility to build attached databases.
+ """
+
+ def __init__(self) -> None:
+ super().__init__(False)
+ APIBasicConfig.read()
+ self.engine = create_engine("sqlite://")
+ self._product = self.engine.connect()
+
+ def attach_db(self, path: str, name: str):
+ self._product.execute(text(f"attach database '{path}' as {name};"))
+ def attach_autosubmit_db(self):
+ autosubmit_db_path = os.path.abspath(APIBasicConfig.DB_PATH)
+ self.attach_db(autosubmit_db_path, "autosubmit")
-def create_autosubmit_db_engine():
+ def attach_as_times_db(self):
+ as_times_db_path = os.path.join(
+ APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB
+ )
+ self.attach_db(as_times_db_path, "as_times")
+
+ @property
+ def product(self) -> Connection:
+ return super().product
+
+
+def create_main_db_conn() -> Connection:
+ """
+ Connection with the autosubmit and as_times DDBB.
+ """
+ builder = AttachedDatabaseConnBuilder()
+ builder.attach_autosubmit_db()
+ builder.attach_as_times_db()
+
+ return builder.product
+
+
+def create_autosubmit_db_engine() -> Engine:
+ """
+ Create an engine for the autosubmit DDBB. Usually named autosubmit.db
+ """
APIBasicConfig.read()
return create_engine(f"sqlite:///{ os.path.abspath(APIBasicConfig.DB_PATH)}")
-def create_as_times_db_engine():
+def create_as_times_db_engine() -> Engine:
+ """
+ Create an engine for the AS_TIMES DDBB. Usually named as_times.db
+ """
APIBasicConfig.read()
db_path = os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB)
return create_engine(f"sqlite:///{ os.path.abspath(db_path)}")
diff --git a/autosubmit_api/database/data/autosubmit.sql b/autosubmit_api/database/data/autosubmit.sql
deleted file mode 100644
index f1a1332c8fbff9963c0abb9159bace80ce055797..0000000000000000000000000000000000000000
--- a/autosubmit_api/database/data/autosubmit.sql
+++ /dev/null
@@ -1,8 +0,0 @@
-CREATE TABLE experiment(
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- name VARCHAR NOT NULL,
- description VARCHAR NOT NULL,
- autosubmit_version VARCHAR);
-CREATE TABLE db_version(
- version INTEGER NOT NULL);
-INSERT INTO db_version (version) VALUES (1);
\ No newline at end of file
diff --git a/autosubmit_api/database/db_common.py b/autosubmit_api/database/db_common.py
index d9f714bca27e4860d1ddc25612df6c18f078e269..961bba69346eac7098dea4aca257d0f19c7b8eb3 100644
--- a/autosubmit_api/database/db_common.py
+++ b/autosubmit_api/database/db_common.py
@@ -22,7 +22,7 @@ Module containing functions to manage autosubmit's database.
"""
import os
from sqlite3 import Connection, Cursor
-import pysqlite3 as sqlite3
+import sqlite3
from bscearth.utils.log import Log
from autosubmit_api.config.basicConfig import APIBasicConfig
@@ -96,7 +96,7 @@ def open_conn(check_version=True) -> Tuple[Connection, Cursor]:
return conn, cursor
-def close_conn(conn, cursor):
+def close_conn(conn: Connection, cursor):
"""
Commits changes and close connection to database
@@ -231,7 +231,7 @@ def search_experiment_by_id(query, exp_type=None, only_active=None, owner=None):
experiment_times = dict()
if len(table) > 0:
experiment_status = DbRequests.get_experiment_status()
- experiment_times = DbRequests.get_experiment_times()
+ # REMOVED: experiment_times = DbRequests.get_experiment_times()
for row in table:
expid = str(row[1])
@@ -268,7 +268,7 @@ def search_experiment_by_id(query, exp_type=None, only_active=None, owner=None):
try:
current_run = ExperimentHistoryDirector(ExperimentHistoryBuilder(expid)).build_reader_experiment_history().manager.get_experiment_run_dc_with_max_id()
- if current_run and current_run.total > 0 and (current_run.total == total or current_run.modified_timestamp > last_modified_timestamp):
+ if current_run and current_run.total > 0:
completed = current_run.completed
total = current_run.total
submitted = current_run.submitted
@@ -313,7 +313,7 @@ def get_current_running_exp():
experiment_status = dict()
experiment_times = dict()
experiment_status = DbRequests.get_experiment_status()
- experiment_times = DbRequests.get_experiment_times()
+ # REMOVED: experiment_times = DbRequests.get_experiment_times()
for row in table:
expid = str(row[1])
status = "NOT RUNNING"
@@ -353,7 +353,7 @@ def get_current_running_exp():
# Try to retrieve experiment_run data
try:
current_run = ExperimentHistoryDirector(ExperimentHistoryBuilder(expid)).build_reader_experiment_history().manager.get_experiment_run_dc_with_max_id()
- if current_run and current_run.total > 0 and (current_run.total == total or current_run.modified_timestamp > last_modified_timestamp):
+ if current_run and current_run.total > 0:
completed = current_run.completed
total = current_run.total
submitted = current_run.submitted
diff --git a/autosubmit_api/database/db_jobdata.py b/autosubmit_api/database/db_jobdata.py
index d9fb4e17a0a9e01d92cadf98e259f60f905ddd83..25a70ad81b4495657b388e5e5303206c67613ff0 100644
--- a/autosubmit_api/database/db_jobdata.py
+++ b/autosubmit_api/database/db_jobdata.py
@@ -21,7 +21,7 @@ import os
import time
import textwrap
import traceback
-import pysqlite3 as sqlite3
+import sqlite3
import collections
import portalocker
from datetime import datetime, timedelta
@@ -38,6 +38,8 @@ from autosubmit_api.common.utils import get_jobs_with_no_outliers, Status, datec
# import autosubmitAPIwu.experiment.common_db_requests as DbRequests
from bscearth.utils.date import Log
+from autosubmit_api.persistance.experiment import ExperimentPaths
+
# Version 15 includes out err MaxRSS AveRSS and rowstatus
CURRENT_DB_VERSION = 15 # Used to be 10 or 0
@@ -532,9 +534,9 @@ class ExperimentGraphDrawing(MainDataBase):
MainDataBase.__init__(self, expid)
APIBasicConfig.read()
self.expid = expid
+ exp_paths = ExperimentPaths(expid)
self.folder_path = APIBasicConfig.LOCAL_ROOT_DIR
- self.database_path = os.path.join(
- self.folder_path, "as_metadata", "graph" , "graph_data_" + str(expid) + ".db")
+ self.database_path = exp_paths.graph_data_db
self.create_table_query = textwrap.dedent(
'''CREATE TABLE
IF NOT EXISTS experiment_graph_draw (
@@ -620,9 +622,10 @@ class ExperimentGraphDrawing(MainDataBase):
result = graph.create('dot', format="plain")
for u in result.split(b"\n"):
splitList = u.split(b" ")
- if len(splitList) > 1 and splitList[0] == "node":
- self.coordinates.append((splitList[1], int(
- float(splitList[2]) * 90), int(float(splitList[3]) * -90)))
+ if len(splitList) > 1 and splitList[0].decode() == "node":
+
+ self.coordinates.append((splitList[1].decode(), int(
+ float(splitList[2].decode()) * 90), int(float(splitList[3].decode()) * -90)))
# self.coordinates[splitList[1]] = (
# int(float(splitList[2]) * 90), int(float(splitList[3]) * -90))
self.insert_coordinates()
@@ -734,7 +737,7 @@ class ExperimentGraphDrawing(MainDataBase):
class JobDataStructure(MainDataBase):
- def __init__(self, expid, basic_config):
+ def __init__(self, expid: str, basic_config: APIBasicConfig):
"""Initializes the object based on the unique identifier of the experiment.
Args:
@@ -744,8 +747,8 @@ class JobDataStructure(MainDataBase):
# BasicConfig.read()
# self.expid = expid
self.folder_path = basic_config.JOBDATA_DIR
- self.database_path = os.path.join(
- self.folder_path, "job_data_" + str(expid) + ".db")
+ exp_paths = ExperimentPaths(expid)
+ self.database_path = exp_paths.job_data_db
# self.conn = None
self.db_version = None
# self.jobdata_list = JobDataList(self.expid)
diff --git a/autosubmit_api/database/db_structure.py b/autosubmit_api/database/db_structure.py
index 87fa177659b86f68bcd79aeadfb1f2d03cc82ee7..0866488dc470ff71fad8d81dc93cff953d530a1e 100644
--- a/autosubmit_api/database/db_structure.py
+++ b/autosubmit_api/database/db_structure.py
@@ -1,7 +1,9 @@
import os
import textwrap
import traceback
-import pysqlite3 as sqlite3
+import sqlite3
+
+from autosubmit_api.persistance.experiment import ExperimentPaths
def get_structure(expid, structures_path):
"""
@@ -12,10 +14,10 @@ def get_structure(expid, structures_path):
:rtype: Dictionary Key: String, Value: List(of String)
"""
try:
+ exp_paths = ExperimentPaths(expid)
+ db_structure_path = exp_paths.structure_db
#pkl_path = os.path.join(exp_path, expid, "pkl")
- if os.path.exists(structures_path):
- db_structure_path = os.path.join(
- structures_path, "structure_" + expid + ".db")
+ if os.path.exists(db_structure_path):
# Create file
os.umask(0)
if not os.path.exists(db_structure_path):
@@ -51,8 +53,8 @@ def get_structure(expid, structures_path):
return dict()
else:
# pkl folder not found
- raise Exception("structures folder not found " +
- str(structures_path))
+ raise Exception("structures db not found " +
+ str(db_structure_path))
except Exception as exp:
print((traceback.format_exc()))
@@ -70,7 +72,7 @@ def create_connection(db_file):
return None
-def create_table(conn, create_table_sql):
+def create_table(conn: sqlite3.Connection, create_table_sql):
""" create a table from the create_table_sql statement
:param conn: Connection object
:param create_table_sql: a CREATE TABLE statement
@@ -100,59 +102,3 @@ def _get_exp_structure(path):
except Exception as exp:
print((traceback.format_exc()))
return dict()
-
-
-def save_structure(graph, exp_id, structures_path):
- """
- Saves structure if path is valid
- """
- #pkl_path = os.path.join(exp_path, exp_id, "pkl")
- if os.path.exists(structures_path):
- db_structure_path = os.path.join(
- structures_path, "structure_" + exp_id + ".db")
- # with open(db_structure_path, "w"):
- conn = create_connection(db_structure_path)
- deleted = _delete_table_content(conn)
- if deleted == True:
- nodes_edges = {u for u, v in graph.edges()}
- nodes_edges.update({v for u, v in graph.edges()})
- independent_nodes = {
- u for u in graph.nodes() if u not in nodes_edges}
- data = {(u, v) for u, v in graph.edges()}
- data.update({(u, u) for u in independent_nodes})
- # save
- _create_edge(conn, data)
- #print("Created edge " + str(u) + str(v))
- conn.commit()
- else:
- # pkl folder not found
- raise Exception("pkl folder not found " + str(structures_path))
-
-
-def _create_edge(conn, data):
- """
- Create edge
- """
- try:
- sql = ''' INSERT INTO experiment_structure(e_from, e_to) VALUES(?,?) '''
- cur = conn.cursor()
- cur.executemany(sql, data)
- # return cur.lastrowid
- except sqlite3.Error as e:
- print(("Error on Insert : " + str(type(e).__name__)))
-
-
-def _delete_table_content(conn):
- """
- Deletes table content
- """
- try:
- sql = ''' DELETE FROM experiment_structure '''
- cur = conn.cursor()
- cur.execute(sql)
- conn.commit()
- return True
- except Exception as exp:
- # print(traceback.format_exc())
- print(("Error on Delete _delete_table_content: {}".format(str(exp))))
- return False
diff --git a/autosubmit_api/database/extended_db.py b/autosubmit_api/database/extended_db.py
deleted file mode 100644
index d2469013909652d5578bbd71cbfda3181f461c38..0000000000000000000000000000000000000000
--- a/autosubmit_api/database/extended_db.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from autosubmit_api.config.basicConfig import APIBasicConfig
-from autosubmit_api.database.db_manager import DbManager
-from autosubmit_api.experiment.common_db_requests import prepare_completed_times_db, prepare_status_db
-from autosubmit_api.workers.populate_details.populate import DetailsProcessor
-
-class ExtendedDB:
- def __init__(self, root_path: str, db_name: str, as_times_db_name: str) -> None:
- self.root_path = root_path
- self.db_name = db_name
- self.main_db_manager = DbManager(root_path, db_name)
- self.as_times_db_manager = DbManager(root_path, as_times_db_name)
-
- def prepare_db(self):
- """
- Create tables and views that are required
- """
- self.prepare_main_db()
- self.prepare_as_times_db()
-
-
- def prepare_main_db(self):
- APIBasicConfig.read()
- DetailsProcessor(APIBasicConfig).create_details_table_if_not_exists()
- self.main_db_manager.create_view(
- 'listexp',
- 'select id,name,user,created,model,branch,hpc,description from experiment left join details on experiment.id = details.exp_id'
- )
-
- def prepare_as_times_db(self):
- prepare_completed_times_db()
- prepare_status_db()
- self.as_times_db_manager.create_view('currently_running',
- 'select s.name, s.status, t.total_jobs from experiment_status as s inner join experiment_times as t on s.name = t.name where s.status="RUNNING" ORDER BY t.total_jobs'
- )
-
-
diff --git a/autosubmit_api/database/models.py b/autosubmit_api/database/models.py
index db4a3e09eb5fa6427d8c1ee64e8e39480dd8c525..c63b2b056be1392b3b4bc8a2dc780d6eeaf81175 100644
--- a/autosubmit_api/database/models.py
+++ b/autosubmit_api/database/models.py
@@ -2,7 +2,7 @@ from typing import Optional
from pydantic import BaseModel
-class Experiment(BaseModel):
+class ExperimentModel(BaseModel):
id: int
name: str
description: str
diff --git a/autosubmit_api/database/queries.py b/autosubmit_api/database/queries.py
index d23d684908090957a5e68cfeab535cdb275a038e..ad3e3278658386af3b540adb1a7158981e9bc1a6 100644
--- a/autosubmit_api/database/queries.py
+++ b/autosubmit_api/database/queries.py
@@ -20,9 +20,6 @@ def generate_query_listexp_extended(
select(
tables.experiment_table,
tables.details_table,
- tables.experiment_times_table.c.exp_id,
- tables.experiment_times_table.c.total_jobs,
- tables.experiment_times_table.c.completed_jobs,
tables.experiment_status_table.c.exp_id,
tables.experiment_status_table.c.status,
)
@@ -31,11 +28,6 @@ def generate_query_listexp_extended(
tables.experiment_table.c.id == tables.details_table.c.exp_id,
isouter=True,
)
- .join(
- tables.experiment_times_table,
- tables.experiment_table.c.id == tables.experiment_times_table.c.exp_id,
- isouter=True,
- )
.join(
tables.experiment_status_table,
tables.experiment_table.c.id == tables.experiment_status_table.c.exp_id,
diff --git a/autosubmit_api/database/tables.py b/autosubmit_api/database/tables.py
index bc3913d34c9e3d25f5b415679112a278c414cf0a..7a42573b5b774d59c7cd6ff25c84f449042f526d 100644
--- a/autosubmit_api/database/tables.py
+++ b/autosubmit_api/database/tables.py
@@ -28,17 +28,6 @@ details_table = Table(
# AS_TIMES TABLES
-experiment_times_table = Table(
- "experiment_times",
- metadata_obj,
- Column("exp_id", Integer, primary_key=True),
- Column("name", Text, nullable=False),
- Column("created", Integer, nullable=False),
- Column("modified", Integer, nullable=False),
- Column("total_jobs", Integer, nullable=False),
- Column("completed_jobs", Integer, nullable=False),
-)
-
experiment_status_table = Table(
"experiment_status",
metadata_obj,
@@ -48,3 +37,15 @@ experiment_status_table = Table(
Column("seconds_diff", Integer, nullable=False),
Column("modified", Text, nullable=False),
)
+
+
+# Graph Data TABLES
+
+graph_data_table = Table(
+ "experiment_graph_draw",
+ metadata_obj,
+ Column("id", Integer, primary_key=True),
+ Column("job_name", Text, nullable=False),
+ Column("x", Integer, nullable=False),
+ Column("y", Integer, nullable=False),
+)
diff --git a/autosubmit_api/experiment/common_db_requests.py b/autosubmit_api/experiment/common_db_requests.py
index 752d5e8a78c1b078863e39bcb1cd1d54b3adf4f7..801e7e4447946140990bc2853c4f6d1b60e6f05d 100644
--- a/autosubmit_api/experiment/common_db_requests.py
+++ b/autosubmit_api/experiment/common_db_requests.py
@@ -1,11 +1,7 @@
import os
-import time
-import textwrap
import traceback
import sqlite3
from datetime import datetime
-from collections import OrderedDict
-from typing import List, Tuple, Dict
from autosubmit_api.config.basicConfig import APIBasicConfig
APIBasicConfig.read()
@@ -15,98 +11,8 @@ DB_FILES_STATUS = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, "as_metadata", "te
# PATH_DB_DATA = "/esarchive/autosubmit/as_metadata/data/"
-def prepare_status_db():
- """
- Creates table of experiment status if it does not exist.
- :return: Map from experiment name to (Id of experiment, Status, Seconds)
- :rtype: Dictionary Key: String, Value: Integer, String, Integer
- """
- conn = create_connection(DB_FILE_AS_TIMES)
- create_table_query = textwrap.dedent(
- '''CREATE TABLE
- IF NOT EXISTS experiment_status (
- exp_id integer PRIMARY KEY,
- name text NOT NULL,
- status text NOT NULL,
- seconds_diff integer NOT NULL,
- modified text NOT NULL,
- FOREIGN KEY (exp_id) REFERENCES experiment (id)
- );''')
- # drop_table_query = ''' DROP TABLE experiment_status '''
- # create_table(conn, drop_table_query)
- create_table(conn, create_table_query)
- current_table = _get_exp_status()
- current_table_expid = dict()
- # print(current_table)
- # print(type(current_table))
- for item in current_table:
- exp_id, expid, status, seconds = item
- current_table_expid[expid] = (exp_id, status, seconds)
- return current_table_expid
-
-
-def prepare_completed_times_db():
- """
- Creates two tables:
- "experiment_times" that stores general information about experiments' jobs completed updates
- "job_times" that stores information about completed time for jobs in the database as_times.db
- Then retrieves all the experiments data from "experiment_times".
- :return: Dictionary that maps experiment name to experiment data.
- :rtype: Dictionary: Key String, Value 6-tuple (exp_id, name, created, modified, total_jobs, completed_jobs)
- """
- conn = create_connection(DB_FILE_AS_TIMES)
- create_table_header_query = textwrap.dedent(
- '''CREATE TABLE
- IF NOT EXISTS experiment_times (
- exp_id integer PRIMARY KEY,
- name text NOT NULL,
- created int NOT NULL,
- modified int NOT NULL,
- total_jobs int NOT NULL,
- completed_jobs int NOT NULL,
- FOREIGN KEY (exp_id) REFERENCES experiment (id)
- );''')
-
- create_table_detail_query = textwrap.dedent(
- '''CREATE TABLE
- IF NOT EXISTS job_times (
- detail_id integer PRIMARY KEY AUTOINCREMENT,
- exp_id integer NOT NULL,
- job_name text NOT NULL,
- created integer NOT NULL,
- modified integer NOT NULL,
- submit_time int NOT NULL,
- start_time int NOT NULL,
- finish_time int NOT NULL,
- status text NOT NULL,
- FOREIGN KEY (exp_id) REFERENCES experiment (id)
- );''')
-
- drop_table_header_query = ''' DROP TABLE experiment_times '''
- drop_table_details_query = ''' DROP TABLE job_times '''
- # create_table(conn, drop_table_details_query)
- # create_table(conn, drop_table_header_query)
- # return
- create_table(conn, create_table_header_query)
- create_table(conn, create_table_detail_query)
- current_table = _get_exp_times()
- current_table_expid = dict()
-
- for item in current_table:
- try:
- exp_id, name, created, modified, total_jobs, completed_jobs = item
- current_table_expid[name] = (exp_id, int(created), int(
- modified), int(total_jobs), int(completed_jobs))
- except Exception as ex:
- print((traceback.format_exc()))
- print(item)
- print((str(name) + " ~ " + str(created) + "\t" + str(modified)))
-
- return current_table_expid
-
# STATUS ARCHIVE
-
def insert_archive_status(status, alatency, abandwidth, clatency, cbandwidth, rtime):
try:
@@ -146,99 +52,6 @@ def get_last_read_archive_status():
# INSERTIONS
-
-def insert_experiment_times_header(expid, timest, total_jobs, completed_jobs, debug=False, log=None):
- """
- Inserts into experiment times header. Requires ecearth.db connection.
- :param expid: Experiment name
- :type expid: String
- :param timest: timestamp of the pkl last modified date
- :type timest: Integer
- :param total_jobs: Total number of jobs
- :type total_jobs: Integer
- :param completed_jobs: Number of completed jobs
- :type completed_jobs: Integer
- :param debug: Flag (testing purposes)
- :type debug: Boolean
- :param conn_ecearth: ecearth.db connection
- :type conn: sqlite3 connection
- :return: exp_id of the experiment (not the experiment name)
- :rtype: Integer
- """
- try:
- current_id = _get_id_db(create_connection(APIBasicConfig.DB_PATH), expid)
- if (current_id):
- if (debug == True):
- print(("INSERTING INTO EXPERIMENT_TIMES " + str(current_id) + "\t" + str(expid) +
- "\t" + str(timest) + "\t" + str(total_jobs) + "\t" + str(completed_jobs)))
- return current_id
- row_content = (current_id, expid, int(timest), int(timest), total_jobs, completed_jobs)
- result = _create_exp_times(row_content)
- return current_id
- else:
- return -1
- except sqlite3.Error as e:
- print(("Error on Insert : " + str(type(e).__name__)))
- print(current_id)
-
-
-def _create_exp_times(row_content):
- """
- Create experiment times
- :param conn:
- :param details:
- :return:
- """
- try:
- conn = create_connection(DB_FILE_AS_TIMES)
- sql = ''' INSERT OR REPLACE INTO experiment_times(exp_id, name, created, modified, total_jobs, completed_jobs) VALUES(?,?,?,?,?,?) '''
- # print(row_content)
- cur = conn.cursor()
- cur.execute(sql, row_content)
- # print(cur)
- conn.commit()
- return cur.lastrowid
- except sqlite3.Error as e:
- print(("Error on Insert : " + str(type(e).__name__)))
- print(row_content)
-
-
-def create_many_job_times(list_job):
- try:
- conn = create_connection(DB_FILE_AS_TIMES)
- sql = ''' INSERT INTO job_times(exp_id, job_name, created, modified, submit_time, start_time, finish_time, status) VALUES(?,?,?,?,?,?,?,?) '''
- # print(row_content)
- cur = conn.cursor()
- cur.executemany(sql, list_job)
- # print(cur)
- # Commit outside the loop
- conn.commit()
- except sqlite3.Error as e:
- print((traceback.format_exc()))
- print(("Error on Insert : " + str(type(e).__name__)))
- # print((exp_id, job_name, timest, timest,
- # submit_time, start_time, finish_time, status))
-
-
-def _insert_into_ecearth_details(exp_id, user, created, model, branch, hpc):
- """
- Inserts into the details table of ecearth.db
- :return: Id
- :rtype: int
- """
- conn = create_connection(APIBasicConfig.DB_PATH)
- if conn:
- try:
- sql = ''' INSERT INTO details(exp_id, user, created, model, branch, hpc) VALUES(?,?,?,?,?,?) '''
- cur = conn.cursor()
- cur.execute(sql, (exp_id, user, created, model, branch, hpc))
- conn.commit()
- return cur.lastrowid
- except Exception as exp:
- print(exp)
- return False
-
-
def create_connection(db_file):
# type: (str) -> sqlite3.Connection
"""
@@ -253,63 +66,8 @@ def create_connection(db_file):
print(exp)
-def create_table(conn, create_table_sql):
- """ create a table from the create_table_sql statement
- :param conn: Connection object
- :param create_table_sql: a CREATE TABLE statement
- :return:
- """
- try:
- c = conn.cursor()
- c.execute(create_table_sql)
- except Exception as e:
- print(e)
-
-
# SELECTS
-def get_times_detail(exp_id):
- """
- Gets the current detail of the experiment from job_times
- :param exp_id: Id of the experiment
- :type exp_id: Integer
- :return: Dictionary Key: Job Name, Values: 5-tuple (submit time, start time, finish time, status, detail id)
- :rtype: dict
- """
- # conn = create_connection(DB_FILE_AS_TIMES)
- try:
- current_table_detail = dict()
- current_table = _get_job_times(exp_id)
- if current_table is None:
- return None
- for item in current_table:
- detail_id, exp_id, job_name, created, modified, submit_time, start_time, finish_time, status = item
- current_table_detail[job_name] = (
- submit_time, start_time, finish_time, status, detail_id)
- return current_table_detail
- except Exception as exp:
- print((traceback.format_exc()))
- return None
-
-
-def get_times_detail_by_expid(conn, expid):
- """
- Gets the detail of times of the experiment by expid (name of experiment).\n
- :param conn: ecearth.db connection
- :rtype conn: sqlite3 connection
- :param expid: Experiment name
- :type expid: str
- :return: Dictionary Key: Job Name, Values: 5-tuple (submit time, start time, finish time, status, detail id)
- :rtype: dict
- """
- # conn = create_connection(DB_FILE_AS_TIMES)
- exp_id = _get_id_db(conn, expid)
- if (exp_id):
- return get_times_detail(exp_id)
- else:
- return None
-
-
def get_experiment_status():
"""
Gets table experiment_status as dictionary
@@ -324,22 +82,6 @@ def get_experiment_status():
return experiment_status
-def get_currently_running_experiments():
- """
- Gets the list of currently running experiments ordered by total_jobs
- Connects to AS_TIMES
- :return: map of expid -> total_jobs
- :rtype: OrderedDict() str -> int
- """
- experiment_running = OrderedDict()
- current_running = _get_exp_only_active()
- if current_running:
- for item in current_running:
- name, status, total_jobs = item
- experiment_running[name] = total_jobs
- return experiment_running
-
-
def get_specific_experiment_status(expid):
"""
Gets the current status from database.\n
@@ -353,287 +95,6 @@ def get_specific_experiment_status(expid):
return (name, status)
-def get_experiment_times():
- # type: () -> Dict[str, Tuple[int, int, int]]
- """
- Gets table experiment_times as dictionary
- conn is expected to reference as_times.db
- """
- # conn = create_connection(DB_FILE_AS_TIMES)
- experiment_times = dict()
- current_table = _get_exp_times()
- for item in current_table:
- exp_id, name, created, modified, total_jobs, completed_jobs = item
- experiment_times[name] = (total_jobs, completed_jobs, modified)
- # if extended == True:
- # experiment_times[name] = (total_jobs, completed_jobs, created, modified)
- return experiment_times
-
-
-def get_experiment_times_by_expid(expid):
- """[summary]
- :return: exp_id, total number of jobs, number of completed jobs
- :rtype 3-tuple: (int, int, int)
- """
- current_row = _get_exp_times_by_expid(expid)
- if current_row:
- exp_id, name, created, modified, total_jobs, completed_jobs = current_row
- return (exp_id, total_jobs, completed_jobs)
- return None
-
-
-def get_experiment_times_group():
- """
- Gets table experiment_times as dictionary id -> name
- conn is expected to reference as_times.db
- """
- experiment_times = dict()
- current_table = _get_exp_times()
- for item in current_table:
- exp_id, name, created, modified, total_jobs, completed_jobs = item
- if name not in list(experiment_times.keys()):
- experiment_times[name] = list()
- experiment_times[name].append(exp_id)
- # if extended == True:
- # experiment_times[name] = (total_jobs, completed_jobs, created, modified)
- return experiment_times
-
-
-def get_exps_base():
- """
- Get exp name and id from experiment table in ecearth.db
- :param conn: ecearth.db connection
- :param expid:
- :return:
- """
- conn = create_connection(APIBasicConfig.DB_PATH)
- result = dict()
- conn.text_factory = str
- cur = conn.cursor()
- cur.execute(
- "SELECT name, id FROM experiment WHERE autosubmit_version IS NOT NULL")
- rows = cur.fetchall()
- cur.close()
- conn.close()
- if (rows):
- for row in rows:
- _name, _id = row
- result[_name] = _id
- else:
- return None
- return result
-
-
-def get_exps_detailed_complete():
- """
- Get information from details table
- :param conn: ecearth.db connection
- :param expid:
- :return: Dictionary exp_id -> (user, created, model, branch, hpc)
- """
- all_details = _get_exps_detailed_complete(create_connection(APIBasicConfig.DB_PATH))
- result = dict()
- if (all_details):
- for item in all_details:
- exp_id, user, created, model, branch, hpc = item
- result[exp_id] = (user, created, model, branch, hpc)
- return result
-
-
-def get_latest_completed_jobs(seconds=300):
- """
- Get latest completed jobs
- """
- result = dict()
- latest_completed_detail = _get_latest_completed_jobs(seconds)
- if (latest_completed_detail):
- for item in latest_completed_detail:
- detail_id, exp_id, job_name, created, modified, submit_time, start_time, finish_time, status = item
- result[job_name] = (detail_id, submit_time,
- start_time, finish_time, status)
- return result
-
-
-def _get_exps_detailed_complete(conn):
- """
- Get information from details table
- :param conn: ecearth.db connection
- :param expid:
- :return:
- """
- conn.text_factory = str
- cur = conn.cursor()
- cur.execute(
- "SELECT * FROM details")
- rows = cur.fetchall()
- cur.close()
- conn.close()
- return rows
-
-
-def _get_exp_times():
- # type: () -> List[Tuple[int, str, int, int, int, int]]
- """
- Get all experiments from table experiment_times.\n
- :return: Row content (exp_id, name, created, modified, total_jobs, completed_jobs)
- :rtype: 6-tuple (int, str, int, int, int, int)
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- conn.text_factory = str
- cur = conn.cursor()
- cur.execute(
- "SELECT exp_id, name, created, modified, total_jobs, completed_jobs FROM experiment_times")
- rows = cur.fetchall()
- conn.close()
- return rows
- except Exception as exp:
- print((traceback.format_exc()))
- return list()
-
-
-def _get_exp_times_by_expid(expid):
- """
- Returns data from experiment_time table by expid.
-
- :param expid: expid/name
- :type expid: str
- :return: Row content (exp_id, name, created, modified, total_jobs, completed_jobs)
- :rtype: 6-tuple (int, str, str, str, int, int)
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- conn.text_factory = str
- cur = conn.cursor()
- cur.execute(
- "SELECT exp_id, name, created, modified, total_jobs, completed_jobs FROM experiment_times WHERE name=?", (expid,))
- rows = cur.fetchall()
- conn.close()
- return rows[0] if len(rows) > 0 else None
- except Exception as exp:
- print((traceback.format_exc()))
- return None
-
-
-def _get_job_times(exp_id):
- """
- Get exp job times detail for a given expid from job_times.
- :param exp_id: Experiment id (not name)
- :type exp_id: Integer
- :return: Detail content
- :rtype: list of 9-tuple: (detail_id, exp_id, job_name, created, modified, submit_time, start_time, finish_time, status)
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- cur = conn.cursor()
- cur.execute("SELECT detail_id, exp_id, job_name, created, modified, submit_time, start_time, finish_time, status FROM job_times WHERE exp_id=?", (exp_id,))
- rows = cur.fetchall()
- conn.close()
- return rows
- except Exception as ex:
- print((traceback.format_exc()))
- print((str(exp_id)))
- print((str(ex)))
- return None
-
-
-def _get_latest_completed_jobs(seconds=300):
- """
- get latest completed jobs, defaults at 300 seconds, 5 minutes
- """
- current_ts = time.time()
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- cur = conn.cursor()
- cur.execute("SELECT detail_id, exp_id, job_name, created, modified, submit_time, start_time, finish_time, status FROM job_times WHERE (? - modified)<=? AND status='COMPLETED'", (current_ts, seconds))
- rows = cur.fetchall()
- conn.close()
- return rows
- except Exception as ex:
- print((traceback.format_exc()))
- print((str(seconds)))
- print((str(ex)))
- return None
-
-
-def _delete_many_from_job_times_detail(detail_list):
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- cur = conn.cursor()
- #print("Cursor defined")
- cur.executemany(
- "DELETE FROM job_times WHERE detail_id=?", detail_list)
- # print(cur.rowcount)
- cur.close()
- conn.commit()
- conn.close()
- # No reliable way to get any feedback from cursor at this point, so let's just return 1
- return True
- except Exception as exp:
- # print("Error while trying to delete " +
- # str(detail_id) + " from job_times.")
- print((traceback.format_exc()))
- return None
-
-
-def delete_experiment_data(exp_id):
- # type: (int) -> bool
- """
- Deletes experiment data from experiment_times and job_times in as_times.db
-
- :param exp_id: Id of experiment
- :type exp_id: int
- """
- deleted_e = _delete_from_experiment_times(exp_id)
- deleted_j = _delete_from_job_times(exp_id)
- print(("Main exp " + str(deleted_e) + "\t" + str(deleted_j)))
- if deleted_e and deleted_j:
- return True
- return False
-
-
-def _delete_from_experiment_times(exp_id):
- """
- Deletes an experiment from experiment_times in as_times.db
-
- :param exp_id: Id of experiment
- :type exp_id: int
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- cur = conn.cursor()
- cur.execute("DELETE FROM experiment_times WHERE exp_id=?", (exp_id,))
- conn.commit()
- conn.close()
- return True
- except Exception as exp:
- print(("Error while trying to delete " +
- str(exp_id) + " from experiment_times."))
- print((traceback.format_exc()))
- return None
-
-
-def _delete_from_job_times(exp_id):
- """
- Deletes an experiment from job_times in as_times.db
-
- :param exp_id: Id of experiment
- :type exp_id: int
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- cur = conn.cursor()
- cur.execute("DELETE FROM job_times WHERE exp_id=?", (exp_id,))
- conn.commit()
- conn.close()
- return True
- except Exception as exp:
- print(("Error while trying to delete " +
- str(exp_id) + " from job_times."))
- print((traceback.format_exc()))
- return None
-
-
def _get_exp_status():
"""
Get all registers from experiment_status.\n
@@ -653,23 +114,6 @@ def _get_exp_status():
return dict()
-def _get_exp_only_active():
- """
- Get all registers of experiments ACTIVE
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- conn.text_factory = str
- cur = conn.cursor()
- cur.execute("select name, status, total_jobs from currently_running")
- rows = cur.fetchall()
- return rows
- except Exception as exp:
- print((traceback.format_exc()))
- print(exp)
- return None
-
-
def _get_specific_exp_status(expid):
"""
Get all registers from experiment_status.\n
@@ -692,167 +136,4 @@ def _get_specific_exp_status(expid):
return (0, expid, "NOT RUNNING", 0)
-def _get_id_db(conn, expid):
- """
- Get exp_id of the experiment (different than the experiment name).
- :param conn: ecearth.db connection
- :type conn: sqlite3 connection
- :param expid: Experiment name
- :type expid: String
- :return: Id of the experiment
- :rtype: Integer or None
- """
- try:
- cur = conn.cursor()
- cur.execute("SELECT id FROM experiment WHERE name=?", (expid,))
- row = cur.fetchone()
- return row[0]
- except Exception as exp:
- print(("Couldn't get exp_id for {0}".format(expid)))
- print(traceback.format_exc())
- return None
-
-
# UPDATES
-
-
-def update_exp_status(expid, status, seconds_diff):
- """
- Update existing experiment_status.
- :param expid: Experiment name
- :type expid: String
- :param status: Experiment status
- :type status: String
- :param seconds_diff: Indicator of how long it has been active since the last time it was checked
- :type seconds_diff: Integer
- :return: Id of register
- :rtype: Integer
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- sql = ''' UPDATE experiment_status SET status = ?, seconds_diff = ?, modified = ? WHERE name = ? '''
- cur = conn.cursor()
- cur.execute(sql, (status, seconds_diff,
- datetime.today().strftime('%Y-%m-%d-%H:%M:%S'), expid))
- conn.commit()
- return cur.lastrowid
- except sqlite3.Error as e:
- print(("Error while trying to update " +
- str(expid) + " in experiment_status."))
- print((traceback.format_exc()))
- print(("Error on Update: " + str(type(e).__name__)))
-
-
-def _update_ecearth_details(exp_id, user, created, model, branch, hpc):
- """
- Updates ecearth.db table details.
- """
- conn = create_connection(APIBasicConfig.DB_PATH)
- if conn:
- try:
- sql = ''' UPDATE details SET user=?, created=?, model=?, branch=?, hpc=? where exp_id=? '''
- cur = conn.cursor()
- cur.execute(sql, (user, created, model, branch, hpc, exp_id))
- conn.commit()
- return True
- except Exception as exp:
- print(exp)
- return False
- return False
-
-
-def update_experiment_times(exp_id, modified, completed_jobs, total_jobs, debug=False):
- """
- Update existing experiment times header
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- if (debug == True):
- print(("UPDATE experiment_times " + str(exp_id) + "\t" +
- str(completed_jobs) + "\t" + str(total_jobs)))
- return
- sql = ''' UPDATE experiment_times SET modified = ?, completed_jobs = ?, total_jobs = ? WHERE exp_id = ? '''
- cur = conn.cursor()
- cur.execute(sql, (int(modified), completed_jobs, total_jobs, exp_id))
- conn.commit()
- # print("Updated header")
- return exp_id
- except sqlite3.Error as e:
- print(("Error while trying to update " +
- str(exp_id) + " in experiment_times."))
- print((traceback.format_exc()))
- print(("Error on Update: " + str(type(e).__name__)))
-
-
-def update_experiment_times_only_modified(exp_id, modified):
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- sql = ''' UPDATE experiment_times SET modified = ? WHERE exp_id = ? '''
- cur = conn.cursor()
- cur.execute(sql, (int(modified), exp_id))
- conn.commit()
- # print("Updated header")
- return exp_id
- except sqlite3.Error as e:
- print(("Error while trying to update " +
- str(exp_id) + " in experiment_times."))
- print((traceback.format_exc()))
- print(("Error on Update: " + str(type(e).__name__)))
-
-
-def update_job_times(detail_id, modified, submit_time, start_time, finish_time, status, debug=False, no_modify_time=False):
- """
- Update single experiment job detail \n
- :param conn: Connection to as_times.db. \n
- :type conn: sqlite3 connection. \n
- :param detail_id: detail_id in as_times.job_times. \n
- :type detail_id: Integer. \n
- :param modified: Timestamp of current date of pkl \n
- :type modified: Integer. \n
- """
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- if (debug == True):
- print(("UPDATING JOB TIMES " + str(detail_id) + " ~ " + str(modified) + "\t" +
- str(submit_time) + "\t" + str(start_time) + "\t" + str(finish_time) + "\t" + str(status)))
- return
- if no_modify_time == False:
- sql = ''' UPDATE job_times SET modified = ?, submit_time = ?, start_time = ?, finish_time = ?, status = ? WHERE detail_id = ? '''
- cur = conn.cursor()
- cur.execute(sql, (modified, submit_time, start_time,
- finish_time, status, detail_id))
- # Commit outside the loop
- conn.commit()
- cur.close()
- conn.close()
- else:
- sql = ''' UPDATE job_times SET submit_time = ?, start_time = ?, finish_time = ?, status = ? WHERE detail_id = ? '''
- cur = conn.cursor()
- cur.execute(sql, (submit_time, start_time,
- finish_time, status, detail_id))
- # Commit outside the loop
- conn.commit()
- cur.close()
- conn.close()
-
- except sqlite3.Error as e:
- print(("Error while trying to update " +
- str(detail_id) + " in job_times."))
- print((traceback.format_exc()))
- print(("Error on Update: " + str(type(e).__name__)))
-
-
-def update_many_job_times(list_jobs):
- try:
- conn = create_connection(os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.AS_TIMES_DB))
- sql = ''' UPDATE job_times SET modified = ?, submit_time = ?, start_time = ?, finish_time = ?, status = ? WHERE detail_id = ? '''
- cur = conn.cursor()
- cur.executemany(sql, list_jobs)
- # Commit outside the loop
- conn.commit()
- cur.close()
- conn.close()
- except sqlite3.Error as e:
- print("Error while trying to update many in update_many_job_times.")
- print((traceback.format_exc()))
- print(("Error on Update: " + str(type(e).__name__)))
diff --git a/autosubmit_api/experiment/common_requests.py b/autosubmit_api/experiment/common_requests.py
index dce9f43513c8f12385cedf647fb844d4d5ae53ac..0028357e2741388767409e58024102091d66e7b7 100644
--- a/autosubmit_api/experiment/common_requests.py
+++ b/autosubmit_api/experiment/common_requests.py
@@ -43,6 +43,7 @@ from autosubmit_api.logger import logger
from autosubmit_api.performance.utils import calculate_SYPD_perjob
from autosubmit_api.monitor.monitor import Monitor
+from autosubmit_api.persistance.experiment import ExperimentPaths
from autosubmit_api.statistics.statistics import Statistics
@@ -172,7 +173,8 @@ def get_experiment_data(expid):
result["completed_jobs"] = experiment_run.completed
result["db_historic_version"] = experiment_history.manager.db_version
else:
- _, result["total_jobs"], result["completed_jobs"] = DbRequests.get_experiment_times_by_expid(expid)
+ result["total_jobs"] = 0
+ result["completed_jobs"] = 0
result["db_historic_version"] = "NA"
except Exception as exp:
@@ -211,10 +213,9 @@ def _is_exp_running(expid, time_condition=300):
definite_log_path = None
try:
APIBasicConfig.read()
- pathlog_aslog = APIBasicConfig.LOCAL_ROOT_DIR + '/' + expid + '/' + \
- APIBasicConfig.LOCAL_TMP_DIR + '/' + APIBasicConfig.LOCAL_ASLOG_DIR
- pathlog_tmp = APIBasicConfig.LOCAL_ROOT_DIR + '/' + \
- expid + '/' + APIBasicConfig.LOCAL_TMP_DIR
+ exp_paths = ExperimentPaths(expid)
+ pathlog_aslog = exp_paths.tmp_as_logs_dir
+ pathlog_tmp = exp_paths.tmp_dir
# Basic Configuration
look_old_folder = False
current_version = None
@@ -479,7 +480,8 @@ def get_experiment_log_last_lines(expid):
try:
APIBasicConfig.read()
- path = APIBasicConfig.LOCAL_ROOT_DIR + '/' + expid + '/' + APIBasicConfig.LOCAL_TMP_DIR + '/' + APIBasicConfig.LOCAL_ASLOG_DIR
+ exp_paths = ExperimentPaths(expid)
+ path = exp_paths.tmp_as_logs_dir
reading = os.popen('ls -t ' + path + ' | grep "run.log"').read() if (os.path.exists(path)) else ""
# Finding log files
@@ -528,7 +530,8 @@ def get_job_log(expid, logfile, nlines=150):
logcontent = []
reading = ""
APIBasicConfig.read()
- logfilepath = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, expid, APIBasicConfig.LOCAL_TMP_DIR, "LOG_{0}".format(expid), logfile)
+ exp_paths = ExperimentPaths(expid)
+ logfilepath = os.path.join(exp_paths.tmp_log_dir, logfile)
try:
if os.path.exists(logfilepath):
current_stat = os.stat(logfilepath)
@@ -726,7 +729,7 @@ def get_experiment_tree_rundetail(expid, run_id):
print((traceback.format_exc()))
return {'tree': [], 'jobs': [], 'total': 0, 'reference': [], 'error': True, 'error_message': str(e), 'pkl_timestamp': 0}
base_list['error'] = False
- base_list['error_message'] = 'None'
+ base_list['error_message'] = ''
base_list['pkl_timestamp'] = pkl_timestamp
return base_list
@@ -769,53 +772,6 @@ def get_experiment_tree_structured(expid, log):
return {'tree': [], 'jobs': [], 'total': 0, 'reference': [], 'error': True, 'error_message': str(e), 'pkl_timestamp': 0}
-def verify_last_completed(seconds=300):
- """
- Verifying last 300 seconds by default
- """
- # Basic info
- t0 = time.time()
- APIBasicConfig.read()
- # Current timestamp
- current_st = time.time()
- # Connection
- path = APIBasicConfig.LOCAL_ROOT_DIR
- db_file = os.path.join(path, DbRequests.DB_FILE_AS_TIMES)
- conn = DbRequests.create_connection(db_file)
- # Current latest detail
- td0 = time.time()
- latest_detail = DbRequests.get_latest_completed_jobs(seconds)
- t_data = time.time() - td0
- # Main Loop
- for job_name, detail in list(latest_detail.items()):
- tmp_path = os.path.join(
- APIBasicConfig.LOCAL_ROOT_DIR, job_name[:4], APIBasicConfig.LOCAL_TMP_DIR)
- detail_id, submit, start, finish, status = detail
- submit_time, start_time, finish_time, status_text_res = JUtils.get_job_total_stats(
- common_utils.Status.COMPLETED, job_name, tmp_path)
- submit_ts = int(time.mktime(submit_time.timetuple())) if len(
- str(submit_time)) > 0 else 0
- start_ts = int(time.mktime(start_time.timetuple())) if len(
- str(start_time)) > 0 else 0
- finish_ts = int(time.mktime(finish_time.timetuple())) if len(
- str(finish_time)) > 0 else 0
- if (finish_ts != finish):
- #print("\tMust Update")
- DbRequests.update_job_times(detail_id,
- int(current_st),
- submit_ts,
- start_ts,
- finish_ts,
- status,
- debug=False,
- no_modify_time=True)
- t1 = time.time()
- # Timer safeguard
- if (t1 - t0) > SAFE_TIME_LIMIT:
- raise Exception(
- "Time limit reached {0:06.2f} seconds on verify_last_completed while reading {1}. Time spent on reading data {2:06.2f} seconds.".format((t1 - t0), job_name, t_data))
-
-
def get_experiment_counters(expid):
"""
Returns status counters of the experiment.
@@ -863,7 +819,8 @@ def get_quick_view(expid):
total_count = completed_count = failed_count = running_count = queuing_count = 0
try:
APIBasicConfig.read()
- path_to_logs = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, expid, "tmp", "LOG_" + expid)
+ exp_paths = ExperimentPaths(expid)
+ path_to_logs = exp_paths.tmp_log_dir
# Retrieving packages
now_ = time.time()
@@ -952,7 +909,8 @@ def get_job_history(expid, job_name):
result = None
try:
APIBasicConfig.read()
- path_to_job_logs = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, expid, "tmp", "LOG_" + expid)
+ exp_paths = ExperimentPaths(expid)
+ path_to_job_logs = exp_paths.tmp_log_dir
result = ExperimentHistoryDirector(ExperimentHistoryBuilder(expid)).build_reader_experiment_history().get_historic_job_data(job_name)
except Exception as exp:
print((traceback.format_exc()))
diff --git a/autosubmit_api/experiment/experiment_db_manager.py b/autosubmit_api/experiment/experiment_db_manager.py
deleted file mode 100644
index f07f39df6d4dc2da96aa39b8d7d9e42299b8c9eb..0000000000000000000000000000000000000000
--- a/autosubmit_api/experiment/experiment_db_manager.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# References BasicConfig.DB_PATH
-
-import history.database_managers.database_models as Models
-from history.database_managers.database_manager import DatabaseManager
-from config.basicConfig import APIBasicConfig
-from typing import List
-
-class ExperimentDbManager(DatabaseManager):
- # The current implementation only handles the experiment table. The details table is ignored because it is handled by a different worker.
- def __init__(self, basic_config, expid):
- # type: (APIBasicConfig, str) -> None
- super(ExperimentDbManager, self).__init__(expid, basic_config)
- self.basic_config = basic_config
- self._ecearth_file_path = self.basic_config.DB_PATH
-
- def get_experiment_row_by_expid(self, expid):
- # type: (str) -> Models.ExperimentRow | None
- """
- Get the experiment from ecearth.db by expid as Models.ExperimentRow.
- """
- statement = self.get_built_select_statement("experiment", "name=?")
- current_rows = self.get_from_statement_with_arguments(self._ecearth_file_path, statement, (expid,))
- if len(current_rows) <= 0:
- return None
- # raise ValueError("Experiment {0} not found in {1}".format(expid, self._ecearth_file_path))
- return Models.ExperimentRow(*current_rows[0])
-
- def get_experiments_with_valid_version(self):
- # type: () -> List[Models.ExperimentRow]
- statement = self.get_built_select_statement("experiment", "autosubmit_version IS NOT NULL")
- rows = self.get_from_statement(self._ecearth_file_path, statement)
- return [Models.ExperimentRow(*row) for row in rows]
-
- # def insert_experiment_details(self, exp_id, user, created, model, branch, hpc):
-
- # statement = ''' INSERT INTO details(exp_id, user, created, model, branch, hpc) VALUES(?,?,?,?,?,?) '''
- # arguments = (exp_id, user, status, 0, HUtils.get_current_datetime())
- # return self.insert_statement_with_arguments(self._as_times_file_path, statement, arguments)
\ No newline at end of file
diff --git a/autosubmit_api/experiment/old_code.txt b/autosubmit_api/experiment/old_code.txt
deleted file mode 100644
index d9cac6a20157ecd71fc3a9a8f85f340f664028ac..0000000000000000000000000000000000000000
--- a/autosubmit_api/experiment/old_code.txt
+++ /dev/null
@@ -1,412 +0,0 @@
-def get_experiment_metrics(expid):
- """
- Gets metrics
- """
- error = False
- error_message = ""
- SYPD = ASYPD = CHSY = JPSY = RSYPD = Parallelization = 0
- seconds_in_a_day = 86400
- list_considered = []
- core_hours_year = []
- warnings_job_data = []
- total_run_time = 0
- total_queue_time = total_CHSY = total_JPSY = energy_count = 0
-
- try:
- # Basic info
- BasicConfig.read()
- path = BasicConfig.LOCAL_ROOT_DIR + '/' + expid + '/pkl'
- tmp_path = os.path.join(
- BasicConfig.LOCAL_ROOT_DIR, expid, BasicConfig.LOCAL_TMP_DIR)
- pkl_filename = "job_list_" + str(expid) + ".pkl"
- path_pkl = path + "/" + pkl_filename
-
- as_conf = AutosubmitConfig(
- expid, BasicConfig, ConfigParserFactory())
- if not as_conf.check_conf_files():
- # Log.critical('Can not run with invalid configuration')
- raise Exception(
- 'Autosubmit GUI might not have permission to access necessary configuration files.')
-
- # Chunk Information
- chunk_unit = as_conf.get_chunk_size_unit()
- chunk_size = as_conf.get_chunk_size()
- year_per_sim = datechunk_to_year(chunk_unit, chunk_size)
- if year_per_sim <= 0:
- raise Exception("The system couldn't calculate year per SIM value " + str(year_per_sim) +
- ", for chunk size " + str(chunk_size) + " and chunk unit " + str(chunk_unit))
-
- # From database
- # db_file = os.path.join(BasicConfig.LOCAL_ROOT_DIR, "ecearth.db")
- # conn = DbRequests.create_connection(db_file)
- # job_times = DbRequests.get_times_detail_by_expid(conn, expid)
-
- # Job time information
- # Try to get packages
- job_to_package = {}
- package_to_jobs = {}
- job_to_package, package_to_jobs, _, _ = JobList.retrieve_packages(
- BasicConfig, expid)
- # Basic data
- job_running_to_seconds = {}
- # SIM POST TRANSFER jobs (COMPLETED) in experiment
- sim_jobs_in_pkl = []
- post_jobs_in_pkl = []
- transfer_jobs_in_pkl = []
- clean_jobs_in_pkl = []
- sim_jobs_info = []
- post_jobs_info = []
- transfer_jobs_info = []
- clean_jobs_info = []
- sim_jobs_info_asypd = []
- sim_jobs_info_rsypd = []
- outlied = []
- # Get pkl information
- if os.path.exists(path_pkl):
- fd = open(path_pkl, 'r')
- pickle_content = pickle.load(fd)
- # pickle 0: Name, 2: StatusCode
- sim_jobs_in_pkl = [item[0]
- for item in pickle_content if 'SIM' in item[0].split('_') and int(item[2]) == Status.COMPLETED]
- post_jobs_in_pkl = [item[0]
- for item in pickle_content if 'POST' in item[0].split('_') and int(item[2]) == Status.COMPLETED]
- transfer_member = [item[0]
- for item in pickle_content if item[0].find('TRANSFER_MEMBER') > 0 and int(item[2]) == Status.COMPLETED]
- transfer_jobs_in_pkl = [item[0]
- for item in pickle_content if 'TRANSFER' in item[0].split('_') and int(item[2]) == Status.COMPLETED and item[0] not in transfer_member]
- clean_member = [item[0]
- for item in pickle_content if item[0].find('CLEAN_MEMBER') > 0 and int(item[2]) == Status.COMPLETED]
- clean_jobs_in_pkl = [item[0]
- for item in pickle_content if 'CLEAN' in item[0].split('_') and int(item[2]) == Status.COMPLETED and item[0] not in clean_member]
-
- # print(transfer_jobs_in_pkl)
- fakeAllJobs = [SimpleJob(item[0], tmp_path, int(item[2]))
- for item in pickle_content]
- del pickle_content
- job_running_to_seconds, _, warnings_job_data = JobList.get_job_times_collection(
- BasicConfig, fakeAllJobs, expid, job_to_package, package_to_jobs, timeseconds=True)
- # ASYPD - RSYPD warnings
- if len(post_jobs_in_pkl) == 0:
- warnings_job_data.append(
- "ASYPD | There are no (COMPLETED) POST jobs in the experiment, ASYPD cannot be computed.")
- if len(transfer_jobs_in_pkl) == 0 and len(clean_jobs_in_pkl) == 0:
- warnings_job_data.append(
- "RSYPD | There are no TRANSFER nor CLEAN (COMPLETED) jobs in the experiment, RSYPD cannot be computed.")
- if len(transfer_jobs_in_pkl) == 0 and len(clean_jobs_in_pkl) > 0:
- warnings_job_data.append(
- "RSYPD | There are no TRANSFER (COMPLETED) jobs in the experiment, we resort to use (COMPLETED) CLEAN jobs to compute RSYPD.")
-
- Parallelization = 0
- try:
- processors_value = as_conf.get_processors("SIM")
- if processors_value.find(":") >= 0:
- # It is an expression
- components = processors_value.split(":")
- Parallelization = int(sum(
- [math.ceil(float(x) / 36.0) * 36.0 for x in components]))
- warnings_job_data.append("Parallelization parsing | {0} was interpreted as {1} cores.".format(
- processors_value, Parallelization))
- else:
- # It is int
- Parallelization = int(processors_value)
- except Exception as exp:
- # print(exp)
- warnings_job_data.append(
- "CHSY Critical | Autosubmit API could not parse the number of processors for the SIM job.")
- pass
-
- # ASYPD
- # Main Loop
- # Times exist
- if len(job_running_to_seconds) > 0:
- # job_info attributes: ['name', 'queue_time', 'run_time', 'status', 'energy', 'submit', 'start', 'finish', 'ncpus']
- sim_jobs_info = [job_running_to_seconds[job_name]
- for job_name in sim_jobs_in_pkl if job_running_to_seconds.get(job_name, None) is not None]
- sim_jobs_info.sort(key=lambda x: tostamp(x.finish), reverse=True)
-
- # SIM outlier detection
- data_run_times = [job.run_time for job in sim_jobs_info]
- mean_1 = np.mean(data_run_times) if len(data_run_times) > 0 else 0
- std_1 = np.std(data_run_times) if len(data_run_times) > 0 else 0
- threshold = 2
-
- # ASYPD Pre
- post_jobs_info = [job_running_to_seconds[job_name]
- for job_name in post_jobs_in_pkl if job_running_to_seconds.get(job_name, None) is not None and job_running_to_seconds[job_name].finish is not None]
- post_jobs_info.sort(key=lambda x: tostamp(x.finish), reverse=True)
- # End ASYPD Pre
- for job_info in sim_jobs_info:
- # JobRow object
- z_score = (job_info.run_time - mean_1) / \
- std_1 if std_1 > 0 else 0
- # print("{} : {}".format(job_info.name, z_score, threshold))
- if np.abs(z_score) <= threshold and job_info.run_time > 0:
- status = job_info.status if job_info else "UNKNOWN"
- # Energy
- energy = round(job_info.energy, 2) if job_info else 0
- if energy == 0:
- warnings_job_data.append(
- "Considered | Job {0} (Package {1}) has no energy information and is not going to be considered for energy calculations.".format(job_info.name, job_to_package.get(job_info.name, "")))
- total_queue_time += max(int(job_info.queue_time), 0)
- total_run_time += max(int(job_info.run_time), 0)
- seconds_per_year = (Parallelization *
- job_info.run_time) / year_per_sim
- job_JPSY = round(energy / year_per_sim,
- 2) if year_per_sim > 0 else 0
- job_SYPD = round((year_per_sim * seconds_in_a_day) /
- max(int(job_info.run_time), 0), 2) if job_info.run_time > 0 else 0
- job_ASYPD = round((year_per_sim * seconds_in_a_day) / (int(job_info.queue_time) + int(job_info.run_time) + sum(
- job.queue_time + job.run_time for job in post_jobs_info) / len(post_jobs_info)) if len(post_jobs_info) > 0 else 0, 2)
-
- # Maximum finish time
- # max_sim_finish = tostamp(job_info.finish) if job_info.finish is not None and tostamp(
- # job_info.finish) > max_sim_finish else max_sim_finish
- # sim_count += 1
- total_CHSY += round(seconds_per_year / 3600, 2)
- total_JPSY += job_JPSY
- if job_JPSY > 0:
- # Ignore for mean calculation
- energy_count += 1
- # core_hours_year.append(year_seconds/3600)
- list_considered.append(
- {"name": job_info.name,
- "queue": int(job_info.queue_time),
- "running": int(job_info.run_time),
- "CHSY": round(seconds_per_year / 3600, 2),
- "SYPD": job_SYPD,
- "ASYPD": job_ASYPD,
- "JPSY": job_JPSY,
- "energy": energy})
- else:
- # print("Outlied {}".format(job_info.name))
- outlied.append(job_info.name)
- warnings_job_data.append(
- "Outlier | Job {0} (Package {1} - Running time {2} seconds) has been considered an outlier (mean {3}, std {4}, z_score {5}) and will be ignored for performance calculations.".format(job_info.name, job_to_package.get(job_info.name, "NA"), str(job_info.run_time), str(round(mean_1, 2)), str(round(std_1, 2)), str(round(z_score, 2))))
-
- # ASYPD Pre
- sim_jobs_info_asypd = [job for job in sim_jobs_info if job.name not in outlied] if len(
- post_jobs_info) > 0 else []
- sim_jobs_info_asypd.sort(
- key=lambda x: tostamp(x.finish), reverse=True)
-
- # RSYPD
- transfer_jobs_info = [job_running_to_seconds[job_name]
- for job_name in transfer_jobs_in_pkl if job_running_to_seconds.get(job_name, None) is not None and job_running_to_seconds[job_name].finish is not None]
- transfer_jobs_info.sort(
- key=lambda x: tostamp(x.finish), reverse=True)
- if len(transfer_jobs_info) <= 0:
- clean_jobs_info = [job_running_to_seconds[job_name]
- for job_name in clean_jobs_in_pkl if job_running_to_seconds.get(job_name, None) is not None and job_running_to_seconds[job_name].finish is not None]
- clean_jobs_info.sort(
- key=lambda x: tostamp(x.finish), reverse=True)
- sim_jobs_info_rsypd = [job for job in sim_jobs_info if job.name not in outlied and tostamp(job.finish) <= tostamp(
- clean_jobs_info[0].finish)] if len(clean_jobs_info) > 0 else []
- else:
- sim_jobs_info_rsypd = [job for job in sim_jobs_info if job.name not in outlied and tostamp(job.finish) <= tostamp(
- transfer_jobs_info[0].finish)]
- sim_jobs_info_rsypd.sort(
- key=lambda x: tostamp(x.finish), reverse=True)
-
- SYPD = ((year_per_sim * len(list_considered) * seconds_in_a_day) /
- (total_run_time)) if total_run_time > 0 else 0
- SYPD = round(SYPD, 2)
- # Old
- # ASYPD = ((year_per_sim * len(list_considered) * seconds_in_a_day) /
- # (total_run_time + total_queue_time) if (total_run_time +
- # total_queue_time) > 0 else 0)
- # Paper Implementation
- # ASYPD = ((year_per_sim * len(list_considered) * seconds_in_a_day) / (max_sim_finish -
- # min_submit)) if (max_sim_finish - min_submit) > 0 else 0
- # ASYPD New Implementation
- ASYPD = (year_per_sim * len(sim_jobs_info_asypd) * seconds_in_a_day) / (sum(job.queue_time + job.run_time for job in sim_jobs_info_asypd) +
- sum(job.queue_time + job.run_time for job in post_jobs_info) / len(post_jobs_info)) if len(sim_jobs_info_asypd) > 0 and len(post_jobs_info) > 0 else 0
-
- # RSYPD
- RSYPD = 0
- if len(transfer_jobs_info) > 0:
- RSYPD = (year_per_sim * len(sim_jobs_info_rsypd) * seconds_in_a_day) / (tostamp(transfer_jobs_info[0].finish) - tostamp(sim_jobs_info_rsypd[-1].start)) if len(
- sim_jobs_info_rsypd) > 0 and len(transfer_jobs_info) > 0 and (tostamp(transfer_jobs_info[0].finish) - tostamp(sim_jobs_info_rsypd[-1].start)) > 0 else 0
- else:
- RSYPD = (year_per_sim * len(sim_jobs_info_rsypd) * seconds_in_a_day) / (tostamp(clean_jobs_info[0].finish) - tostamp(sim_jobs_info_rsypd[-1].start)) if len(
- sim_jobs_info_rsypd) > 0 and len(clean_jobs_info) > 0 and (tostamp(clean_jobs_info[0].finish) - tostamp(sim_jobs_info_rsypd[-1].start)) > 0 else 0
-
- ASYPD = round(ASYPD, 4)
- RSYPD = round(RSYPD, 4)
- CHSY = round(total_CHSY / len(list_considered),
- 2) if len(list_considered) > 0 else total_CHSY
- JPSY = round(
- total_JPSY / energy_count, 2) if energy_count > 0 else total_JPSY
- except Exception as ex:
- print(traceback.format_exc())
- error = True
- error_message = str(ex)
- pass
-
- return {"SYPD": SYPD,
- "ASYPD": ASYPD,
- "RSYPD": RSYPD,
- "CHSY": CHSY,
- "JPSY": JPSY,
- "Parallelization": Parallelization,
- "considered": list_considered,
- "error": error,
- "error_message": error_message,
- "warnings_job_data": warnings_job_data,
- }
-
-def get_experiment_graph_format_test(expid):
- """
- Some testing. Does not serve any purpose now, but the code might be useful.
- """
- base_list = dict()
- pkl_timestamp = 10000000
- try:
- notransitive = False
- print("Received " + str(expid))
- BasicConfig.read()
- exp_path = os.path.join(BasicConfig.LOCAL_ROOT_DIR, expid)
- tmp_path = os.path.join(exp_path, BasicConfig.LOCAL_TMP_DIR)
- as_conf = AutosubmitConfig(
- expid, BasicConfig, ConfigParserFactory())
- if not as_conf.check_conf_files():
- # Log.critical('Can not run with invalid configuration')
- raise Exception(
- 'Autosubmit GUI might not have permission to access necessary configuration files.')
-
- job_list = Autosubmit.load_job_list(
- expid, as_conf, notransitive=notransitive)
-
- job_list.sort_by_id()
- base_list = job_list.get_graph_representation(BasicConfig)
- except Exception as e:
- return {'nodes': [],
- 'edges': [],
- 'error': True,
- 'error_message': str(e),
- 'graphviz': False,
- 'max_children': 0,
- 'max_parents': 0,
- 'total_jobs': 0,
- 'pkl_timestamp': 0}
- name_to_id = dict()
- # name_to_weight = dict()
- list_nodes = list()
- list_edges = list()
- i = 0
- with open('/home/Earth/wuruchi/Documents/Personal/spectralgraph/data/graph_' + expid + '.txt', 'w') as the_file:
- for item in base_list['nodes']:
- # the_file.write(str(i) + "\n")
- name_to_id[item['id']] = i
- list_nodes.append(i)
- i += 1
- for item in base_list['edges']:
- the_file.write(str(name_to_id[item['from']]) + " " + str(
- name_to_id[item['to']]) + " " + ("10" if item['is_wrapper'] == True else "1") + "\n")
- list_edges.append(
- (name_to_id[item['from']], name_to_id[item['to']]))
- for item in base_list['fake_edges']:
- the_file.write(str(name_to_id[item['from']]) + " " + str(
- name_to_id[item['to']]) + " " + ("10" if item['is_wrapper'] == True else "1") + "\n")
- list_edges.append(
- (name_to_id[item['from']], name_to_id[item['to']]))
- return list_nodes, list_edges
-
-
-
-def update_running_experiments(time_condition=600):
- """
- Tests if an experiment is running and updates database as_times.db accordingly.\n
- :return: Nothing
- """
- t0 = time.time()
- experiment_to_modified_ts = dict() # type: Dict[str, int]
- try:
- BasicConfig.read()
- path = BasicConfig.LOCAL_ROOT_DIR
- # List of experiments from pkl
- tp0 = time.time()
- currentDirectories = subprocess.Popen(['ls', '-t', path],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT) if (os.path.exists(path)) else None
- stdOut, stdErr = currentDirectories.communicate(
- ) if currentDirectories else (None, None)
-
- # Build connection to ecearth.db
- db_file = os.path.join(path, "ecearth.db")
- conn = DbRequests.create_connection(db_file)
- current_table = DbRequests.prepare_status_db()
- readingpkl = stdOut.split() if stdOut else []
-
- tp1 = time.time()
- for expid in readingpkl:
- pkl_path = os.path.join(path, expid, "pkl", "job_list_{0}.pkl".format(expid))
- if not len(expid) == 4 or not os.path.exists(pkl_path):
- continue
- t1 = time.time()
- # Timer safeguard
- if (t1 - t0) > SAFE_TIME_LIMIT_STATUS:
- raise Exception(
- "Time limit reached {0:06.2f} seconds on update_running_experiments while processing {1}. \
- Time spent on reading data {2:06.2f} seconds.".format((t1 - t0), expid, (tp1 - tp0)))
- current_stat = os.stat(pkl_path)
- time_diff = int(time.time()) - int(current_stat.st_mtime)
- if (time_diff < time_condition):
- experiment_to_modified_ts[expid] = time_diff
- if current_table.get(expid, None) is not None:
- # UPDATE RUNNING
- _exp_id, _status, _seconds = current_table[expid]
- if _status != "RUNNING":
- DbRequests.update_exp_status(
- expid, "RUNNING", time_diff)
- else:
- # Insert new experiment
- current_id = DbRequests.insert_experiment_status(
- conn, expid, time_diff)
- current_table[expid] = (
- current_id, 'RUNNING', time_diff)
- elif (time_diff <= 3600):
- # If it has been running in the last 1 hour
- # It must have been added before
- error, error_message, is_running, timediff, _ = _is_exp_running(
- expid)
- if is_running == True:
- if current_table.get(expid, None):
- _exp_id, _status, _seconds = current_table[expid]
- if _status != "RUNNING" and is_running == True:
- DbRequests.update_exp_status(
- expid, "RUNNING", _seconds)
- else:
- current_id = DbRequests.insert_experiment_status(
- conn, expid, time_diff)
- current_table[expid] = (
- current_id, 'RUNNING', time_diff)
-
- for expid in current_table:
- exp_id, status, seconds = current_table[expid]
- if status == "RUNNING" and experiment_to_modified_ts.get(expid, None) is None:
- # Perform exhaustive check
- error, error_message, is_running, timediff, _ = _is_exp_running(
- expid)
- # UPDATE NOT RUNNING
- if (is_running == False):
- # print("Update NOT RUNNING for " + expid)
- DbRequests.update_exp_status(
- expid, "NOT RUNNING", timediff)
- except Exception as e:
- # print(expid)
- print(e.message)
- # print(traceback.format_exc())
-
-def get_experiment_run_detail(expid, run_id):
- error = False
- error_message = ""
- result = None
- try:
- result = JobDataStructure(expid).get_current_run_job_data_json(run_id)
- tags = {"source": JobList.get_sourcetag(), "target": JobList.get_targettag(), "sync": JobList.get_synctag(), "check": JobList.get_checkmark(
- ), "completed": JobList.get_completed_tag(), "running_tag": JobList.get_running_tag(), "queuing_tag": JobList.get_queuing_tag(), "failed_tag": JobList.get_failed_tag()}
- except Exception as exp:
- error = True
- error_message = str(exp)
- pass
- return {"error": error, "error_message": error_message, "rundata": result}
\ No newline at end of file
diff --git a/autosubmit_api/experiment/test.py b/autosubmit_api/experiment/test.py
deleted file mode 100644
index d3e98f75b37feb4be7e9b5b49917f7566f1dc2e1..0000000000000000000000000000000000000000
--- a/autosubmit_api/experiment/test.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015 Earth Sciences Department, BSC-CNS
-
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import unittest
-
-from experiment.common_requests import get_job_history, get_experiment_data
-
-class TestCommonRequests(unittest.TestCase):
- def setUp(self):
- pass
-
- def test_get_history(self):
- result = get_job_history("a3z4", "a3z4_19951101_fc8_1_SIM")
- print(result)
- self.assertTrue(result != None)
-
- def test_get_experiment_data(self):
- result = get_experiment_data("a29z")
- print(result)
- result2 = get_experiment_data("a4a0")
- print(result2)
- result3 = get_experiment_data("a2am")
- print(result3)
- self.assertTrue(result != None)
- self.assertTrue(result2 != None)
- self.assertTrue(result3 != None)
-
-if __name__ == "__main__":
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/history/database_managers/database_manager.py b/autosubmit_api/history/database_managers/database_manager.py
index 0bd01db3db68dcc4b09f25efa3d576cda72d735a..c7cb2d950dac36d35b963754000e8eeb2d8f1e12 100644
--- a/autosubmit_api/history/database_managers/database_manager.py
+++ b/autosubmit_api/history/database_managers/database_manager.py
@@ -16,12 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with Autosubmit. If not, see .
-import pysqlite3 as sqlite3
+import sqlite3
import os
-from .. import utils as HUtils
-from . import database_models as Models
-from ...config.basicConfig import APIBasicConfig
-from abc import ABCMeta, abstractmethod
+from autosubmit_api.history import utils as HUtils
+from autosubmit_api.history.database_managers import database_models as Models
+from autosubmit_api.config.basicConfig import APIBasicConfig
+from abc import ABCMeta
DEFAULT_JOBDATA_DIR = os.path.join('/esarchive', 'autosubmit', 'as_metadata', 'data')
DEFAULT_HISTORICAL_LOGS_DIR = os.path.join('/esarchive', 'autosubmit', 'as_metadata', 'logs')
diff --git a/autosubmit_api/history/database_managers/experiment_history_db_manager.py b/autosubmit_api/history/database_managers/experiment_history_db_manager.py
index 6d03428da268cd30831df7d2e221a827eeee03a7..1f35a92154782623839b84dbe7c055e34abb4b45 100644
--- a/autosubmit_api/history/database_managers/experiment_history_db_manager.py
+++ b/autosubmit_api/history/database_managers/experiment_history_db_manager.py
@@ -15,16 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with Autosubmit. If not, see .
-import pysqlite3 as sqlite3
import os
-import traceback
import textwrap
-from .. import utils as HUtils
-from .. database_managers import database_models as Models
-from .. data_classes.job_data import JobData
-from ..data_classes.experiment_run import ExperimentRun
-from ...config.basicConfig import APIBasicConfig
-from .database_manager import DatabaseManager, DEFAULT_JOBDATA_DIR
+
+from autosubmit_api.persistance.experiment import ExperimentPaths
+from autosubmit_api.history import utils as HUtils
+from autosubmit_api.history.database_managers import database_models as Models
+from autosubmit_api.history.data_classes.job_data import JobData
+from autosubmit_api.history.data_classes.experiment_run import ExperimentRun
+from autosubmit_api.config.basicConfig import APIBasicConfig
+from autosubmit_api.history.database_managers.database_manager import DatabaseManager
from typing import List
from collections import namedtuple
@@ -39,7 +39,8 @@ class ExperimentHistoryDbManager(DatabaseManager):
super(ExperimentHistoryDbManager, self).__init__(expid, basic_config)
self._set_schema_changes()
self._set_table_queries()
- self.historicaldb_file_path = os.path.join(self.JOBDATA_DIR, "job_data_{0}.db".format(self.expid)) # type : str
+ exp_paths = ExperimentPaths(expid)
+ self.historicaldb_file_path = exp_paths.job_data_db
if self.my_database_exists():
self.set_db_version_models()
@@ -176,10 +177,7 @@ class ExperimentHistoryDbManager(DatabaseManager):
def get_experiment_run_dc_with_max_id(self):
""" Get Current (latest) ExperimentRun data class. """
- if self.db_version >= Models.DatabaseVersion.EXPERIMENT_HEADER_SCHEMA_CHANGES.value:
- return ExperimentRun.from_model(self._get_experiment_run_with_max_id())
- else:
- return ExperimentRun(run_id=0)
+ return ExperimentRun.from_model(self._get_experiment_run_with_max_id())
def register_experiment_run_dc(self, experiment_run_dc):
self._insert_experiment_run(experiment_run_dc)
diff --git a/autosubmit_api/history/database_managers/experiment_status_db_manager.py b/autosubmit_api/history/database_managers/experiment_status_db_manager.py
deleted file mode 100644
index b7092a4653d6f9960a5db03a23d37426634dd41f..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/database_managers/experiment_status_db_manager.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python
-
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import os
-import textwrap
-import time
-
-from autosubmit_api.experiment.common_db_requests import prepare_status_db
-from .database_manager import DatabaseManager, DEFAULT_LOCAL_ROOT_DIR
-from ...config.basicConfig import APIBasicConfig
-from ...history import utils as HUtils
-from ...history.database_managers import database_models as Models
-from typing import List
-
-class ExperimentStatusDbManager(DatabaseManager):
- """ Manages the actions on the status database """
- def __init__(self, expid, basic_config):
- # type: (str, APIBasicConfig) -> None
- super(ExperimentStatusDbManager, self).__init__(expid, basic_config)
- self._as_times_file_path = os.path.join(APIBasicConfig.DB_DIR, self.AS_TIMES_DB_NAME)
- self._ecearth_file_path = os.path.join(APIBasicConfig.DB_DIR, APIBasicConfig.DB_FILE)
- self._pkl_file_path = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, self.expid, "pkl", "job_list_{0}.pkl".format(self.expid))
- self.default_experiment_status_row = Models.ExperimentStatusRow(0, "DEFAULT", "NOT RUNNING", 0, "")
-
-
- def validate_status_database(self):
- """ Creates experiment_status table if it does not exist """
- prepare_status_db()
- # Redundant code
- # create_table_query = textwrap.dedent(
- # '''CREATE TABLE
- # IF NOT EXISTS experiment_status (
- # exp_id integer PRIMARY KEY,
- # name text NOT NULL,
- # status text NOT NULL,
- # seconds_diff integer NOT NULL,
- # modified text NOT NULL
- # );'''
- # )
- # self.execute_statement_on_dbfile(self._as_times_file_path, create_table_query)
-
- def print_current_table(self):
- for experiment in self._get_experiment_status_content():
- print(experiment)
- if self.current_experiment_status_row:
- print(("Current Row:\n\t" + self.current_experiment_status_row.name + "\n\t" +
- str(self.current_experiment_status_row.exp_id) + "\n\t" + self.current_experiment_status_row.status))
-
- def get_experiment_table_content(self):
- # type: () -> List[Models.ExperimentStatusRow]
- return self._get_experiment_status_content()
-
- def is_running(self, time_condition=600):
- # type : (int) -> bool
- """ True if experiment is running, False otherwise. """
- if (os.path.exists(self._pkl_file_path)):
- current_stat = os.stat(self._pkl_file_path)
- timest = int(current_stat.st_mtime)
- timesys = int(time.time())
- time_diff = int(timesys - timest)
- if (time_diff < time_condition):
- return True
- else:
- return False
- return False
-
- def set_existing_experiment_status_as_running(self, expid):
- """ Set the experiment_status row as running. """
- self.update_exp_status(expid, Models.RunningStatus.RUNNING)
-
- def create_experiment_status_as_running(self, experiment):
- """ Create a new experiment_status row for the Models.Experiment item."""
- self.create_exp_status(experiment.id, experiment.name, Models.RunningStatus.RUNNING)
-
-
- def get_experiment_status_row_by_expid(self, expid):
- # type: (str) -> Models.ExperimentStatusRow | None
- """
- Get Models.ExperimentStatusRow by expid. Uses exp_id (int id) as an intermediate step and validation.
- """
- experiment_row = self.get_experiment_row_by_expid(expid)
- return self.get_experiment_status_row_by_exp_id(experiment_row.id) if experiment_row else None
-
- def get_experiment_row_by_expid(self, expid):
- # type: (str) -> Models.ExperimentRow | None
- """
- Get the experiment from ecearth.db by expid as Models.ExperimentRow.
- """
- statement = self.get_built_select_statement("experiment", "name=?")
- current_rows = self.get_from_statement_with_arguments(self._ecearth_file_path, statement, (expid,))
- if len(current_rows) <= 0:
- return None
- # raise ValueError("Experiment {0} not found in {1}".format(expid, self._ecearth_file_path))
- return Models.ExperimentRow(*current_rows[0])
-
- def _get_experiment_status_content(self):
- # type: () -> List[Models.ExperimentStatusRow]
- """
- Get all registers from experiment_status as List of Models.ExperimentStatusRow.\n
- """
- statement = self.get_built_select_statement("experiment_status")
- current_rows = self.get_from_statement(self._as_times_file_path, statement)
- return [Models.ExperimentStatusRow(*row) for row in current_rows]
-
- def get_experiment_status_row_by_exp_id(self, exp_id):
- # type: (int) -> Models.ExperimentStatusRow
- """ Get Models.ExperimentStatusRow from as_times.db by exp_id (int)"""
- statement = self.get_built_select_statement("experiment_status", "exp_id=?")
- arguments = (exp_id,)
- current_rows = self.get_from_statement_with_arguments(self._as_times_file_path, statement, arguments)
- if len(current_rows) <= 0:
- return None
- return Models.ExperimentStatusRow(*current_rows[0])
-
-
- def create_exp_status(self, exp_id, expid, status):
- # type: (int, str, str) -> None
- """
- Create experiment status
- """
- statement = ''' INSERT INTO experiment_status(exp_id, name, status, seconds_diff, modified) VALUES(?,?,?,?,?) '''
- arguments = (exp_id, expid, status, 0, HUtils.get_current_datetime())
- return self.insert_statement_with_arguments(self._as_times_file_path, statement, arguments)
-
- def update_exp_status(self, expid, status="RUNNING"):
- # type: (str, str) -> None
- """
- Update status, seconds_diff, modified in experiment_status.
- """
- statement = ''' UPDATE experiment_status SET status = ?, seconds_diff = ?, modified = ? WHERE name = ? '''
- arguments = (status, 0, HUtils.get_current_datetime(), expid)
- self.execute_statement_with_arguments_on_dbfile(self._as_times_file_path, statement, arguments)
-
- def delete_exp_status(self, expid):
- # type: (str) -> None
- """ Deletes experiment_status row by expid. Useful for testing purposes. """
- statement = ''' DELETE FROM experiment_status where name = ? '''
- arguments = (expid,)
- self.execute_statement_with_arguments_on_dbfile(self._as_times_file_path, statement, arguments)
\ No newline at end of file
diff --git a/autosubmit_api/history/database_managers/test.py b/autosubmit_api/history/database_managers/test.py
deleted file mode 100644
index 1b88b37aa04a6eac228ed2cf363b3ff07addc727..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/database_managers/test.py
+++ /dev/null
@@ -1,261 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import unittest
-import time
-import random
-import os
-from shutil import copy2
-from autosubmit_api.history.database_managers.experiment_history_db_manager import ExperimentHistoryDbManager
-from autosubmit_api.history.database_managers.experiment_status_db_manager import ExperimentStatusDbManager
-# from experiment_status_db_manager import ExperimentStatusDbManager
-from autosubmit_api.history.data_classes.experiment_run import ExperimentRun
-from autosubmit_api.history.data_classes.job_data import JobData
-import autosubmit_api.history.database_managers.database_models as Models
-from autosubmit_api.config.basicConfig import APIBasicConfig
-import autosubmit_api.history.utils as HUtils
-
-EXPID_TT00_SOURCE = "test_database.db~"
-EXPID_TT01_SOURCE = "test_database_no_run.db~"
-EXPID = "t024"
-EXPID_NONE = "t027"
-# BasicConfig.read()
-JOBDATA_DIR = APIBasicConfig.JOBDATA_DIR
-LOCAL_ROOT_DIR = APIBasicConfig.LOCAL_ROOT_DIR
-
-class TestExperimentStatusDatabaseManager(unittest.TestCase):
- """ Covers Experiment Status Database Manager """
- def setUp(self):
- APIBasicConfig.read()
- self.exp_status_db = ExperimentStatusDbManager(EXPID, APIBasicConfig)
- if self.exp_status_db.get_experiment_status_row_by_expid(EXPID) == None:
- self.exp_status_db.create_exp_status(7375, "t024", "NOT RUNNING")
-
-
- def test_get_current_experiment_status_row(self):
- exp_status_row = self.exp_status_db.get_experiment_status_row_by_expid(EXPID)
- self.assertIsNotNone(exp_status_row)
- exp_status_row_none = self.exp_status_db.get_experiment_status_row_by_expid(EXPID_NONE)
- self.assertIsNone(exp_status_row_none)
- exp_row_direct = self.exp_status_db.get_experiment_status_row_by_exp_id(exp_status_row.exp_id)
- self.assertTrue(exp_status_row.exp_id == exp_row_direct.exp_id)
-
-
- def test_update_exp_status(self):
- self.exp_status_db.update_exp_status(EXPID, "RUNNING")
- exp_status_row_current = self.exp_status_db.get_experiment_status_row_by_expid(EXPID)
- self.assertTrue(exp_status_row_current.status == "RUNNING")
- self.exp_status_db.update_exp_status(EXPID, "NOT RUNNING")
- exp_status_row_current = self.exp_status_db.get_experiment_status_row_by_expid(EXPID)
- self.assertTrue(exp_status_row_current.status == "NOT RUNNING")
-
- def test_create_exp_status(self):
- experiment = self.exp_status_db.get_experiment_row_by_expid(EXPID_NONE)
- self.exp_status_db.create_experiment_status_as_running(experiment)
- experiment_status = self.exp_status_db.get_experiment_status_row_by_expid(EXPID_NONE)
- self.assertIsNotNone(experiment_status)
- self.exp_status_db.delete_exp_status(EXPID_NONE)
- experiment_status = self.exp_status_db.get_experiment_status_row_by_expid(EXPID_NONE)
- self.assertIsNone(experiment_status)
-
-
-class TestExperimentHistoryDbManager(unittest.TestCase):
- """ Covers Experiment History Database Manager and Data Models """
- def setUp(self):
- APIBasicConfig.read()
- source_path_tt00 = os.path.join(JOBDATA_DIR, EXPID_TT00_SOURCE)
- self.target_path_tt00 = os.path.join(JOBDATA_DIR, "job_data_{0}.db".format(EXPID))
- copy2(source_path_tt00, self.target_path_tt00)
- source_path_tt01 = os.path.join(JOBDATA_DIR, EXPID_TT01_SOURCE)
- self.target_path_tt01 = os.path.join(JOBDATA_DIR, "job_data_{0}.db".format(EXPID_NONE))
- copy2(source_path_tt01, self.target_path_tt01)
- self.experiment_database = ExperimentHistoryDbManager(EXPID, APIBasicConfig)
- self.experiment_database.initialize()
-
- def tearDown(self):
- os.remove(self.target_path_tt00)
- os.remove(self.target_path_tt01)
-
- def test_get_max_id(self):
- max_item = self.experiment_database.get_experiment_run_dc_with_max_id()
- self.assertTrue(max_item.run_id > 0)
- self.assertTrue(max_item.run_id >= 18) # Max is 18
-
- def test_pragma(self):
- self.assertTrue(self.experiment_database._get_pragma_version() == Models.DatabaseVersion.CURRENT_DB_VERSION.value) # Update version on changes
-
- def test_get_job_data(self):
- job_data = self.experiment_database._get_job_data_last_by_name("a29z_20000101_fc0_1_SIM")
- self.assertTrue(len(job_data) > 0)
- self.assertTrue(job_data[0].last == 1)
- self.assertTrue(job_data[0].job_name == "a29z_20000101_fc0_1_SIM")
-
- job_data = self.experiment_database.get_job_data_dcs_by_name("a29z_20000101_fc0_1_SIM")
- self.assertTrue(job_data[0].job_name == "a29z_20000101_fc0_1_SIM")
-
- job_data = self.experiment_database._get_job_data_last_by_run_id(18) # Latest
- self.assertTrue(len(job_data) > 0)
-
- job_data = self.experiment_database._get_job_data_last_by_run_id_and_finished(18)
- self.assertTrue(len(job_data) > 0)
-
- job_data = self.experiment_database.get_job_data_all()
- self.assertTrue(len(job_data) > 0)
-
- def test_insert_and_delete_experiment_run(self):
- new_run = ExperimentRun(19)
- new_id = self.experiment_database._insert_experiment_run(new_run)
- self.assertIsNotNone(new_id)
- last_experiment_run = self.experiment_database.get_experiment_run_dc_with_max_id()
- self.assertTrue(new_id == last_experiment_run.run_id)
- self.experiment_database.delete_experiment_run(new_id)
- last_experiment_run = self.experiment_database.get_experiment_run_dc_with_max_id()
- self.assertTrue(new_id != last_experiment_run.run_id)
-
- def test_insert_and_delete_job_data(self):
- max_run_id = self.experiment_database.get_experiment_run_dc_with_max_id().run_id
- new_job_data_name = "test_001_name_{0}".format(int(time.time()))
- new_job_data = JobData(_id=1, job_name=new_job_data_name, run_id=max_run_id)
- new_job_data_id = self.experiment_database._insert_job_data(new_job_data)
- self.assertIsNotNone(new_job_data_id)
- self.experiment_database.delete_job_data(new_job_data_id)
- job_data = self.experiment_database.get_job_data_dcs_by_name(new_job_data_name)
- self.assertTrue(len(job_data) == 0)
-
-
- def test_update_experiment_run(self):
- experiment_run_data_class = self.experiment_database.get_experiment_run_dc_with_max_id() # 18
- backup_run = self.experiment_database.get_experiment_run_dc_with_max_id()
- experiment_run_data_class.chunk_unit = "unouno"
- experiment_run_data_class.running = random.randint(1, 100)
- experiment_run_data_class.queuing = random.randint(1, 100)
- experiment_run_data_class.suspended = random.randint(1, 100)
- self.experiment_database._update_experiment_run(experiment_run_data_class)
- last_experiment_run = self.experiment_database.get_experiment_run_dc_with_max_id() # 18
- self.assertTrue(self.experiment_database.db_version == Models.DatabaseVersion.CURRENT_DB_VERSION.value)
- self.assertTrue(last_experiment_run.chunk_unit == experiment_run_data_class.chunk_unit)
- self.assertTrue(last_experiment_run.running == experiment_run_data_class.running)
- self.assertTrue(last_experiment_run.queuing == experiment_run_data_class.queuing)
- self.assertTrue(last_experiment_run.suspended == experiment_run_data_class.suspended)
- self.experiment_database._update_experiment_run(backup_run)
-
- def test_job_data_from_model(self):
- job_data_rows = self.experiment_database._get_job_data_last_by_name("a29z_20000101_fc0_1_SIM")
- job_data_row_first = job_data_rows[0]
- job_data_data_class = JobData.from_model(job_data_row_first)
- self.assertTrue(job_data_row_first.job_name == job_data_data_class.job_name)
-
- def test_update_job_data_processed(self):
- current_time = time.time()
- job_data_rows = self.experiment_database._get_job_data_last_by_name("a29z_20000101_fc0_1_SIM")
- job_data_row_first = job_data_rows[0]
- job_data_data_class = JobData.from_model(job_data_row_first)
- backup_job_dc = JobData.from_model(job_data_row_first)
- job_data_data_class.nnodes = random.randint(1, 1000)
- job_data_data_class.ncpus = random.randint(1, 1000)
- job_data_data_class.status = "DELAYED"
- job_data_data_class.finish = current_time
- self.experiment_database._update_job_data_by_id(job_data_data_class)
- job_data_rows_current = self.experiment_database._get_job_data_last_by_name("a29z_20000101_fc0_1_SIM")
- job_data_row_first = job_data_rows_current[0]
- self.assertTrue(job_data_row_first.nnodes == job_data_data_class.nnodes)
- self.assertTrue(job_data_row_first.ncpus == job_data_data_class.ncpus)
- self.assertTrue(job_data_row_first.status == job_data_data_class.status)
- self.assertTrue(job_data_row_first.finish == job_data_data_class.finish)
- self.experiment_database._update_job_data_by_id(backup_job_dc)
-
- def test_bulk_update(self):
- current_time = time.time()
- all_job_data_rows = self.experiment_database.get_job_data_all()
- job_data_rows_test = [job for job in all_job_data_rows if job.run_id == 3]
- backup = [JobData.from_model(job) for job in job_data_rows_test]
- list_job_data_class = [JobData.from_model(job) for job in job_data_rows_test]
- backup_changes = [(HUtils.get_current_datetime(), job.status, job.rowstatus, job._id) for job in list_job_data_class]
- changes = [(HUtils.get_current_datetime(), "DELAYED", job.rowstatus, job._id) for job in list_job_data_class]
- self.experiment_database.update_many_job_data_change_status(changes)
- all_job_data_rows = self.experiment_database.get_job_data_all()
- job_data_rows_validate = [job for job in all_job_data_rows if job.run_id == 3]
- for (job_val, change_item) in zip(job_data_rows_validate, changes):
- modified, status, rowstatus, _id = change_item
- # self.assertTrue(job_val.finish == finish)
- self.assertTrue(job_val.modified == modified)
- self.assertTrue(job_val.status == status)
- self.assertTrue(job_val.rowstatus == rowstatus)
- self.assertTrue(job_val.id == _id)
- self.experiment_database.update_many_job_data_change_status(backup_changes)
-
- def test_job_data_maxcounter(self):
- new_job_data = ExperimentHistoryDbManager(EXPID_NONE, APIBasicConfig)
- new_job_data.initialize()
- max_empty_table_counter = new_job_data.get_job_data_max_counter()
- self.assertTrue(max_empty_table_counter == 0)
- max_existing_counter = self.experiment_database.get_job_data_max_counter()
- self.assertTrue(max_existing_counter > 0)
-
- def test_register_submitted_job_data_dc(self):
- job_data_dc = self.experiment_database.get_job_data_dc_unique_latest_by_job_name("a29z_20000101_fc0_1_SIM")
- max_counter = self.experiment_database.get_job_data_max_counter()
- self.assertTrue(max_counter > 0)
- self.assertTrue(job_data_dc.counter > 0)
- next_counter = max(max_counter, job_data_dc.counter + 1)
- self.assertTrue(next_counter >= max_counter)
- self.assertTrue(next_counter >= job_data_dc.counter + 1)
- job_data_dc.counter = next_counter
- job_data_dc_current = self.experiment_database.register_submitted_job_data_dc(job_data_dc)
- self.assertTrue(job_data_dc._id < job_data_dc_current._id)
- job_data_last_list = self.experiment_database._get_job_data_last_by_name(job_data_dc.job_name)
- self.assertTrue(len(job_data_last_list) == 1)
- self.experiment_database.delete_job_data(job_data_last_list[0].id)
- job_data_dc.last = 1
- updated_job_data_dc = self.experiment_database.update_job_data_dc_by_id(job_data_dc)
- self.assertTrue(job_data_dc._id == updated_job_data_dc._id)
- job_data_dc = self.experiment_database.get_job_data_dc_unique_latest_by_job_name("a29z_20000101_fc0_1_SIM")
- self.assertTrue(job_data_dc._id == updated_job_data_dc._id)
-
- def test_update_children_and_platform_output(self):
- job_data_dc = self.experiment_database.get_job_data_dc_unique_latest_by_job_name("a29z_20000101_fc0_1_SIM")
- children_str = "a00, a01, a02"
- platform_output_str = " SLURM OUTPUT "
- job_data_dc.children = children_str
- job_data_dc.platform_output = platform_output_str
- self.assertTrue(self.experiment_database.db_version == Models.DatabaseVersion.CURRENT_DB_VERSION.value)
- self.experiment_database.update_job_data_dc_by_id(job_data_dc)
- job_data_dc_updated = self.experiment_database.get_job_data_dc_unique_latest_by_job_name("a29z_20000101_fc0_1_SIM")
- self.assertTrue(job_data_dc_updated.children == children_str)
- self.assertTrue(job_data_dc_updated.platform_output == platform_output_str)
- # Back to normal
- job_data_dc.children = ""
- job_data_dc.platform_output = "NO OUTPUT"
- self.experiment_database.update_job_data_dc_by_id(job_data_dc)
- job_data_dc_updated = self.experiment_database.get_job_data_dc_unique_latest_by_job_name("a29z_20000101_fc0_1_SIM")
- self.assertTrue(job_data_dc_updated.children == "")
- self.assertTrue(job_data_dc_updated.platform_output == "NO OUTPUT")
-
-
-
- def test_experiment_run_dc(self):
- experiment_run = self.experiment_database.get_experiment_run_dc_with_max_id()
- self.assertIsNotNone(experiment_run)
-
- def test_if_database_not_exists(self):
- exp_manager = ExperimentHistoryDbManager("0000", APIBasicConfig)
- self.assertTrue(exp_manager.my_database_exists() == False)
-
-
-if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/history/experiment_history.py b/autosubmit_api/history/experiment_history.py
index f36521d25b3d9fbb1aeb942d64ad8101936159b9..f9ae4231d0196aaabc39f9c1b28ada0fe70a2a69 100644
--- a/autosubmit_api/history/experiment_history.py
+++ b/autosubmit_api/history/experiment_history.py
@@ -15,20 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with Autosubmit. If not, see .
-import os
import traceback
-from .database_managers import database_models as Models
-from ..history import utils as HUtils
-from ..performance import utils as PUtils
-from time import time, sleep
-from ..history.database_managers.experiment_history_db_manager import ExperimentHistoryDbManager
-from ..history.database_managers.database_manager import DEFAULT_JOBDATA_DIR, DEFAULT_HISTORICAL_LOGS_DIR
-from ..history.strategies import PlatformInformationHandler, SingleAssociationStrategy, StraightWrapperAssociationStrategy, TwoDimWrapperDistributionStrategy, GeneralizedWrapperDistributionStrategy
-from ..history.data_classes.job_data import JobData
-from ..history.data_classes.experiment_run import ExperimentRun
-from ..history.platform_monitor.slurm_monitor import SlurmMonitor
-from ..history.internal_logging import Logging
-from ..config.basicConfig import APIBasicConfig
+from autosubmit_api.history.database_managers import database_models as Models
+from autosubmit_api.performance import utils as PUtils
+from autosubmit_api.history.database_managers.experiment_history_db_manager import ExperimentHistoryDbManager
+from autosubmit_api.history.data_classes.job_data import JobData
+from autosubmit_api.history.data_classes.experiment_run import ExperimentRun
+from autosubmit_api.history.internal_logging import Logging
+from autosubmit_api.config.basicConfig import APIBasicConfig
from typing import List, Dict, Tuple, Any
SECONDS_WAIT_PLATFORM = 60
@@ -47,133 +41,11 @@ class ExperimentHistory():
self._log.log(str(exp), traceback.format_exc())
self.manager = None
- def initialize_database(self):
- try:
- self.manager.initialize()
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
- self.manager = None
-
def is_header_ready(self):
if self.manager:
return self.manager.is_header_ready_db_version()
return False
-
- def write_submit_time(self, job_name, submit=0, status="UNKNOWN", ncpus=0, wallclock="00:00", qos="debug", date="",
- member="", section="", chunk=0, platform="NA", job_id=0, wrapper_queue=None, wrapper_code=None, children=""):
- try:
- next_counter = self._get_next_counter_by_job_name(job_name)
- current_experiment_run = self.manager.get_experiment_run_dc_with_max_id()
- job_data_dc = JobData(_id=0,
- counter=next_counter,
- job_name=job_name,
- submit=submit,
- status=status,
- rowtype=self._get_defined_rowtype(wrapper_code),
- ncpus=ncpus,
- wallclock=wallclock,
- qos=self._get_defined_queue_name(wrapper_queue, wrapper_code, qos),
- date=date,
- member=member,
- section=section,
- chunk=chunk,
- platform=platform,
- job_id=job_id,
- children=children,
- run_id=current_experiment_run.run_id)
- return self.manager.register_submitted_job_data_dc(job_data_dc)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
- return None
-
- def write_start_time(self, job_name, start=0, status="UNKWOWN", ncpus=0, wallclock="00:00", qos="debug", date="",
- member="", section="", chunk=0, platform="NA", job_id=0, wrapper_queue=None, wrapper_code=None, children=""):
- try:
- job_data_dc_last = self.manager.get_job_data_dc_unique_latest_by_job_name(job_name)
- if not job_data_dc_last:
- job_data_dc_last = self.write_submit_time(job_name=job_name,
- status=status,
- ncpus=ncpus,
- wallclock=wallclock,
- qos=qos,
- date=date,
- member=member,
- section=section,
- chunk=chunk,
- platform=platform,
- job_id=job_id,
- wrapper_queue=wrapper_queue,
- wrapper_code=wrapper_code)
- self._log.log("write_start_time {0} start not found.".format(job_name))
- job_data_dc_last.start = start
- job_data_dc_last.qos = self._get_defined_queue_name(wrapper_queue, wrapper_code, qos)
- job_data_dc_last.status = status
- job_data_dc_last.rowtype = self._get_defined_rowtype(wrapper_code)
- job_data_dc_last.job_id = job_id
- job_data_dc_last.children = children
- return self.manager.update_job_data_dc_by_id(job_data_dc_last)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
- def write_finish_time(self, job_name, finish=0, status="UNKNOWN", ncpus=0, wallclock="00:00", qos="debug", date="",
- member="", section="", chunk=0, platform="NA", job_id=0, out_file=None, err_file=None,
- wrapper_queue=None, wrapper_code=None, children=""):
- try:
- job_data_dc_last = self.manager.get_job_data_dc_unique_latest_by_job_name(job_name)
- if not job_data_dc_last:
- job_data_dc_last = self.write_submit_time(job_name=job_name,
- status=status,
- ncpus=ncpus,
- wallclock=wallclock,
- qos=qos,
- date=date,
- member=member,
- section=section,
- chunk=chunk,
- platform=platform,
- job_id=job_id,
- wrapper_queue=wrapper_queue,
- wrapper_code=wrapper_code,
- children=children)
- self._log.log("write_finish_time {0} submit not found.".format(job_name))
- job_data_dc_last.finish = finish if finish > 0 else int(time())
- job_data_dc_last.status = status
- job_data_dc_last.job_id = job_id
- job_data_dc_last.rowstatus = Models.RowStatus.PENDING_PROCESS
- job_data_dc_last.out = out_file if out_file else ""
- job_data_dc_last.err = err_file if err_file else ""
- return self.manager.update_job_data_dc_by_id(job_data_dc_last)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
- def write_platform_data_after_finish(self, job_data_dc, platform_obj):
- """
- Call it in a thread.
- """
- try:
- sleep(SECONDS_WAIT_PLATFORM)
- ssh_output = platform_obj.check_job_energy(job_data_dc.job_id)
- slurm_monitor = SlurmMonitor(ssh_output)
- self._verify_slurm_monitor(slurm_monitor, job_data_dc)
- job_data_dcs_in_wrapper = self.manager.get_job_data_dcs_last_by_wrapper_code(job_data_dc.wrapper_code)
- job_data_dcs_to_update = []
- if len(job_data_dcs_in_wrapper) > 0:
- info_handler = PlatformInformationHandler(StraightWrapperAssociationStrategy(self._historiclog_dir_path))
- job_data_dcs_to_update = info_handler.execute_distribution(job_data_dc, job_data_dcs_in_wrapper, slurm_monitor)
- if len(job_data_dcs_to_update) == 0:
- info_handler.strategy = TwoDimWrapperDistributionStrategy(self._historiclog_dir_path)
- job_data_dcs_to_update = info_handler.execute_distribution(job_data_dc, job_data_dcs_in_wrapper, slurm_monitor)
- if len(job_data_dcs_to_update) == 0:
- info_handler.strategy = GeneralizedWrapperDistributionStrategy(self._historiclog_dir_path)
- job_data_dcs_to_update = info_handler.execute_distribution(job_data_dc, job_data_dcs_in_wrapper, slurm_monitor)
- else:
- info_handler = PlatformInformationHandler(SingleAssociationStrategy(self._historiclog_dir_path))
- job_data_dcs_to_update = info_handler.execute_distribution(job_data_dc, job_data_dcs_in_wrapper, slurm_monitor)
- return self.manager.update_list_job_data_dc_by_each_id(job_data_dcs_to_update)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
def get_historic_job_data(self, job_name):
# type: (str) -> List[Dict[str, Any]]
result = []
@@ -231,166 +103,3 @@ class ExperimentHistory():
"err": job_data_dc.err
})
return result
-
-
- def update_job_finish_time_if_zero(self, job_name, finish_ts):
- # type: (str, int) -> JobData
- try:
- job_data_dc = self.manager.get_job_data_dc_unique_latest_by_job_name(job_name)
- if job_data_dc and job_data_dc.finish == 0:
- job_data_dc.finish = finish_ts
- return self.manager.update_job_data_dc_by_id(job_data_dc)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
- def _verify_slurm_monitor(self, slurm_monitor, job_data_dc):
- try:
- if slurm_monitor.header.status not in ["COMPLETED", "FAILED"]:
- self._log.log("Assertion Error on job {0} with ssh_output {1}".format(job_data_dc.job_name, slurm_monitor.original_input),
- "Slurm status {0} is not COMPLETED nor FAILED for ID {1}.\n".format(slurm_monitor.header.status, slurm_monitor.header.name))
- if not slurm_monitor.steps_plus_extern_approximate_header_energy():
- self._log.log("Assertion Error on job {0} with ssh_output {1}".format(job_data_dc.job_name, slurm_monitor.original_input),
- "Steps + extern != total energy for ID {0}. Number of steps {1}.\n".format(slurm_monitor.header.name, slurm_monitor.step_count))
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
- def process_status_changes(self, job_list=None, chunk_unit="NA", chunk_size=0, current_config=""):
- """ Detect status differences between job_list and current job_data rows, and update. Creates a new run if necessary. """
- try:
- current_experiment_run_dc = self.manager.get_experiment_run_dc_with_max_id()
- update_these_changes = self._get_built_list_of_changes(job_list)
- should_create_new_run = self.should_we_create_a_new_run(job_list, len(update_these_changes), current_experiment_run_dc, chunk_unit, chunk_size)
- if len(update_these_changes) > 0 and should_create_new_run == False:
- self.manager.update_many_job_data_change_status(update_these_changes)
- if should_create_new_run:
- return self.create_new_experiment_run(chunk_unit, chunk_size, current_config, job_list)
- return self.update_counts_on_experiment_run_dc(current_experiment_run_dc, job_list)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
- def _get_built_list_of_changes(self, job_list):
- """ Return: List of (current timestamp, current datetime str, status, rowstatus, id in job_data). One tuple per change. """
- job_data_dcs = self.detect_changes_in_job_list(job_list)
- return [(HUtils.get_current_datetime(), job.status, Models.RowStatus.CHANGED, job._id) for job in job_data_dcs]
-
- def process_job_list_changes_to_experiment_totals(self, job_list=None):
- """ Updates current experiment_run row with totals calculated from job_list. """
- try:
- current_experiment_run_dc = self.manager.get_experiment_run_dc_with_max_id()
- return self.update_counts_on_experiment_run_dc(current_experiment_run_dc, job_list)
- except Exception as exp:
- self._log.log(str(exp), traceback.format_exc())
-
- def should_we_create_a_new_run(self, job_list, changes_count, current_experiment_run_dc, new_chunk_unit, new_chunk_size):
- if len(job_list) != current_experiment_run_dc.total:
- return True
- if changes_count > int(self._get_date_member_completed_count(job_list)):
- return True
- return self._chunk_config_has_changed(current_experiment_run_dc, new_chunk_unit, new_chunk_size)
-
- def _chunk_config_has_changed(self, current_exp_run_dc, new_chunk_unit, new_chunk_size):
- if not current_exp_run_dc:
- return True
- if current_exp_run_dc.chunk_unit != new_chunk_unit or current_exp_run_dc.chunk_size != new_chunk_size:
- return True
- return False
-
- def update_counts_on_experiment_run_dc(self, experiment_run_dc, job_list=None):
- """ Return updated row as Models.ExperimentRun. """
- status_counts = self.get_status_counts_from_job_list(job_list)
- experiment_run_dc.completed = status_counts[HUtils.SupportedStatus.COMPLETED]
- experiment_run_dc.failed = status_counts[HUtils.SupportedStatus.FAILED]
- experiment_run_dc.queuing = status_counts[HUtils.SupportedStatus.QUEUING]
- experiment_run_dc.submitted = status_counts[HUtils.SupportedStatus.SUBMITTED]
- experiment_run_dc.running = status_counts[HUtils.SupportedStatus.RUNNING]
- experiment_run_dc.suspended = status_counts[HUtils.SupportedStatus.SUSPENDED]
- experiment_run_dc.total = status_counts["TOTAL"]
- return self.manager.update_experiment_run_dc_by_id(experiment_run_dc)
-
- def finish_current_experiment_run(self):
- if self.manager.is_there_a_last_experiment_run():
- current_experiment_run_dc = self.manager.get_experiment_run_dc_with_max_id()
- current_experiment_run_dc.finish = int(time())
- return self.manager.update_experiment_run_dc_by_id(current_experiment_run_dc)
- return None
-
- def create_new_experiment_run(self, chunk_unit="NA", chunk_size=0, current_config="", job_list=None):
- """ Also writes the finish timestamp of the previous run. """
- self.finish_current_experiment_run()
- return self._create_new_experiment_run_dc_with_counts(chunk_unit=chunk_unit, chunk_size=chunk_size, current_config=current_config, job_list=job_list)
-
- def _create_new_experiment_run_dc_with_counts(self, chunk_unit, chunk_size, current_config="", job_list=None):
- """ Create new experiment_run row and return the new Models.ExperimentRun data class from database. """
- status_counts = self.get_status_counts_from_job_list(job_list)
- experiment_run_dc = ExperimentRun(0,
- chunk_unit=chunk_unit,
- chunk_size=chunk_size,
- metadata=current_config,
- start=int(time()),
- completed=status_counts[HUtils.SupportedStatus.COMPLETED],
- total=status_counts["TOTAL"],
- failed=status_counts[HUtils.SupportedStatus.FAILED],
- queuing=status_counts[HUtils.SupportedStatus.QUEUING],
- running=status_counts[HUtils.SupportedStatus.RUNNING],
- submitted=status_counts[HUtils.SupportedStatus.SUBMITTED],
- suspended=status_counts[HUtils.SupportedStatus.SUSPENDED])
- return self.manager.register_experiment_run_dc(experiment_run_dc)
-
- def detect_changes_in_job_list(self, job_list):
- """ Detect changes in job_list compared to the current contents of job_data table. Returns a list of JobData data classes where the status of each item is the new status."""
- job_name_to_job = {job.name: job for job in job_list}
- current_job_data_dcs = self.manager.get_all_last_job_data_dcs()
- differences = []
- for job_dc in current_job_data_dcs:
- if job_dc.job_name in job_name_to_job and job_dc.status != job_name_to_job[job_dc.job_name].status_str:
- job_dc.status = job_name_to_job[job_dc.job_name].status_str
- differences.append(job_dc)
- return differences
-
- def _get_defined_rowtype(self, code):
- if code:
- return code
- else:
- return Models.RowType.NORMAL
-
- def _get_defined_queue_name(self, wrapper_queue, wrapper_code, qos):
- if wrapper_code and wrapper_code > 2 and wrapper_queue is not None:
- return wrapper_queue
- return qos
-
- def _get_next_counter_by_job_name(self, job_name):
- """ Return the counter attribute from the latest job data row by job_name. """
- job_data_dc = self.manager.get_job_data_dc_unique_latest_by_job_name(job_name)
- max_counter = self.manager.get_job_data_max_counter()
- if job_data_dc:
- return max(max_counter, job_data_dc.counter + 1)
- else:
- return max_counter
-
- def _get_date_member_completed_count(self, job_list):
- """ Each item in the job_list must have attributes: date, member, status_str. """
- job_list = job_list if job_list else []
- return sum(1 for job in job_list if job.date is not None and job.member is not None and job.status_str == HUtils.SupportedStatus.COMPLETED)
-
- def get_status_counts_from_job_list(self, job_list):
- """
- Return dict with keys COMPLETED, FAILED, QUEUING, SUBMITTED, RUNNING, SUSPENDED, TOTAL.
- """
- result = {
- HUtils.SupportedStatus.COMPLETED: 0,
- HUtils.SupportedStatus.FAILED: 0,
- HUtils.SupportedStatus.QUEUING: 0,
- HUtils.SupportedStatus.SUBMITTED: 0,
- HUtils.SupportedStatus.RUNNING: 0,
- HUtils.SupportedStatus.SUSPENDED: 0,
- "TOTAL": 0
- }
-
- if not job_list:
- job_list = []
-
- for job in job_list:
- if job.status_str in result:
- result[job.status_str] += 1
- result["TOTAL"] = len(job_list)
- return result
diff --git a/autosubmit_api/history/experiment_status.py b/autosubmit_api/history/experiment_status.py
deleted file mode 100644
index d9046aaab3adbc776fb7cd846ab688dd9c50f339..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/experiment_status.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import traceback
-from .database_managers.experiment_status_db_manager import ExperimentStatusDbManager
-from .database_managers.database_manager import DEFAULT_LOCAL_ROOT_DIR, DEFAULT_HISTORICAL_LOGS_DIR
-from .internal_logging import Logging
-from ..config.basicConfig import APIBasicConfig
-from typing import List
-from .database_managers.database_models import ExperimentStatusRow
-
-class ExperimentStatus():
- """ Represents the Experiment Status Mechanism that keeps track of currently active experiments """
- def __init__(self, expid):
- # type: (str) -> None
- self.expid = expid # type: str
- print(expid)
- APIBasicConfig.read()
- try:
- self.manager = ExperimentStatusDbManager(self.expid, APIBasicConfig)
- except Exception as exp:
- message = "Error while trying to update {0} in experiment_status.".format(str(self.expid))
- print(message)
- print(str(exp))
- print()
- Logging(self.expid, APIBasicConfig).log(message, traceback.format_exc())
- self.manager = None
-
- def validate_database(self):
- # type: () -> None
- self.manager.validate_status_database()
-
- def get_current_table_content(self):
- # type: () -> List[ExperimentStatusRow]
- return self.manager.get_experiment_table_content()
-
- def set_as_running(self):
- # type: () -> ExperimentStatusRow
- """ Set the status of the experiment in experiment_status of as_times.db as RUNNING. Inserts row if necessary."""
- if not self.manager:
- raise Exception("ExperimentStatus: The database manager is not available.")
- exp_status_row = self.manager.get_experiment_status_row_by_expid(self.expid)
- if exp_status_row:
- self.manager.set_existing_experiment_status_as_running(exp_status_row.name)
- return self.manager.get_experiment_status_row_by_expid(self.expid)
- else:
- exp_row = self.manager.get_experiment_row_by_expid(self.expid)
- if exp_row:
- self.manager.create_experiment_status_as_running(exp_row)
- return self.manager.get_experiment_status_row_by_expid(self.expid)
- else:
- print(("Couldn't find {} in the main database. There is not enough information to set it as running.".format(self.expid)))
- return self.manager.default_experiment_status_row
-
-
- def set_as_not_running(self):
- # type: () -> None
- """ Deletes row by expid. """
- if not self.manager:
- raise Exception("ExperimentStatus: The database manager is not available.")
- exp_status_row = self.manager.get_experiment_status_row_by_expid(self.expid)
- if not exp_status_row:
- # raise Exception("ExperimentStatus: Query error, experiment {} not found in status table.".format(self.expid))
- pass # If it is not in the status table, we don't need to worry about it.
- else:
- self.manager.delete_exp_status(exp_status_row.name)
diff --git a/autosubmit_api/history/experiment_status_manager.py b/autosubmit_api/history/experiment_status_manager.py
deleted file mode 100644
index 8a79477c0c9445a835221115aea35d8071af57ab..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/experiment_status_manager.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import os
-import time
-import subprocess
-from .experiment_status import ExperimentStatus
-from ..config.basicConfig import APIBasicConfig
-from ..experiment.common_requests import _is_exp_running
-from ..common.utils import get_experiments_from_folder
-from typing import Dict, Set
-from .utils import SAFE_TIME_LIMIT_STATUS
-from . import utils as HUtils
-
-
-
-class ExperimentStatusManager(object):
- """ Manages the update of the status table. """
- def __init__(self):
- APIBasicConfig.read()
- self._basic_config = APIBasicConfig
- self._experiments_updated = set()
- self._local_root_path = self._basic_config.LOCAL_ROOT_DIR
- self._base_experiment_status = ExperimentStatus("0000")
- self._base_experiment_status.validate_database()
- self._validate_configuration()
- self._creation_timestamp = int(time.time())
-
- def _validate_configuration(self):
- # type: () -> None
- if not os.path.exists(self._local_root_path):
- raise Exception("Experiment Status Manager: LOCAL ROOT DIR not found.")
-
- def update_running_experiments(self, time_condition=600):
- # type: (int) -> None
- """
- Tests if an experiment is running and updates database as_times.db accordingly.\n
- :return: Nothing
- """
-
- # start_reading_folders = int(time.time())
- # currentDirectories = subprocess.Popen(['ls', '-t', self._local_root_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- # stdOut, _ = currentDirectories.communicate()
- # readingpkl = stdOut.split()
- # time_reading_folders = int(time.time()) - start_reading_folders
- readingpkl = get_experiments_from_folder(self._local_root_path)
- # Update those that are RUNNING
- for expid in readingpkl:
- pkl_path = os.path.join(self._local_root_path, str(expid), "pkl", "job_list_{0}.pkl".format(str(expid)))
- if not len(expid) == 4 or not os.path.exists(pkl_path):
- continue
- time_spent = int(time.time()) - self._creation_timestamp
- if time_spent > SAFE_TIME_LIMIT_STATUS:
- raise Exception(
- "Time limit reached {0} seconds on update_running_experiments while processing {1}. \
- Time spent on reading data {2} seconds.".format(time_spent, expid, time_reading_folders))
- time_diff = int(time.time()) - int(os.stat(pkl_path).st_mtime)
- if (time_diff < time_condition):
- self._experiments_updated.add(ExperimentStatus(expid).set_as_running().exp_id)
- elif (time_diff <= 3600):
- _, _ , is_running, _, _ = _is_exp_running(expid) # Exhaustive validation
- if is_running == True:
- self._experiments_updated.add(ExperimentStatus(expid).set_as_running().exp_id)
- # Update those that were RUNNING
- self._detect_and_delete_not_running()
-
- def _detect_and_delete_not_running(self):
- # type: () -> None
- current_rows = self._base_experiment_status.get_current_table_content()
- for experiment_status_row in current_rows:
- if experiment_status_row.status == "RUNNING" and experiment_status_row.exp_id not in self._experiments_updated:
- _, _, is_running, _, _ = _is_exp_running(experiment_status_row.name) # Exhaustive validation
- if is_running == False:
- ExperimentStatus(experiment_status_row.name).set_as_not_running()
\ No newline at end of file
diff --git a/autosubmit_api/history/platform_monitor/output_examples/pending.txt b/autosubmit_api/history/platform_monitor/output_examples/pending.txt
deleted file mode 100644
index 007e88d0886e0514329881d82c47db9c4ae5109e..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/output_examples/pending.txt
+++ /dev/null
@@ -1 +0,0 @@
- 17838842 PENDING 4 1 2021-10-11T10:55:53 Unknown Unknown
diff --git a/autosubmit_api/history/platform_monitor/output_examples/wrapper1.txt b/autosubmit_api/history/platform_monitor/output_examples/wrapper1.txt
deleted file mode 100644
index 61b855cd1b46e5c03a8e8298f333d8315a5b4b2e..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/output_examples/wrapper1.txt
+++ /dev/null
@@ -1,3 +0,0 @@
- 12535498 COMPLETED 2 1 2020-11-18T13:54:24 2020-11-18T13:55:55 2020-11-18T13:56:10 2.77K
- 12535498.batch COMPLETED 2 1 2020-11-18T13:55:55 2020-11-18T13:55:55 2020-11-18T13:56:10 2.69K 659K 659K
- 12535498.extern COMPLETED 2 1 2020-11-18T13:55:55 2020-11-18T13:55:55 2020-11-18T13:56:10 2.77K 24K 24K
\ No newline at end of file
diff --git a/autosubmit_api/history/platform_monitor/output_examples/wrapper2.txt b/autosubmit_api/history/platform_monitor/output_examples/wrapper2.txt
deleted file mode 100644
index 082eb0105bf17788d6f9a058f6530b74490e0faa..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/output_examples/wrapper2.txt
+++ /dev/null
@@ -1,3 +0,0 @@
- 12535498 COMPLETED 2 1 2020-11-18T13:54:24 2020-11-18T13:55:55 2020-11-18T13:56:10 2.77K
- 12535498.batch COMPLETED 2 1 2020-11-18T13:55:55 2020-11-18T13:55:55 2020-11-18T13:56:10 2.69K 659K 659K
- 12535498.0 COMPLETED 2 1 2020-11-18T13:55:55 2020-11-18T13:55:55 2020-11-18T13:56:10 2.77K 24K 24K
\ No newline at end of file
diff --git a/autosubmit_api/history/platform_monitor/output_examples/wrapper_big.txt b/autosubmit_api/history/platform_monitor/output_examples/wrapper_big.txt
deleted file mode 100644
index 65c6c119128b035bdb21cb2133838c5353ab9bbd..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/output_examples/wrapper_big.txt
+++ /dev/null
@@ -1,33 +0,0 @@
- 17857525 COMPLETED 10 1 2021-10-13T15:51:16 2021-10-13T15:51:17 2021-10-13T15:52:47 19.05K
- 17857525.batch COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 13.38K 6264K 6264K
- 17857525.extern COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 13.66K 473K 68K
- 17857525.0 COMPLETED 10 1 2021-10-13T15:51:21 2021-10-13T15:51:21 2021-10-13T15:51:22 186 352K 312.30K
- 17857525.1 COMPLETED 10 1 2021-10-13T15:51:23 2021-10-13T15:51:23 2021-10-13T15:51:24 186 420K 306.70K
- 17857525.2 COMPLETED 10 1 2021-10-13T15:51:24 2021-10-13T15:51:24 2021-10-13T15:51:27 188 352K 325.80K
- 17857525.3 COMPLETED 10 1 2021-10-13T15:51:28 2021-10-13T15:51:28 2021-10-13T15:51:29 192 352K 341.90K
- 17857525.4 COMPLETED 10 1 2021-10-13T15:51:29 2021-10-13T15:51:29 2021-10-13T15:51:31 186 352K 335.20K
- 17857525.5 COMPLETED 10 1 2021-10-13T15:51:31 2021-10-13T15:51:31 2021-10-13T15:51:32 186 352K 329.80K
- 17857525.6 COMPLETED 10 1 2021-10-13T15:51:32 2021-10-13T15:51:32 2021-10-13T15:51:33 184 428K 311.10K
- 17857525.7 COMPLETED 10 1 2021-10-13T15:51:34 2021-10-13T15:51:34 2021-10-13T15:51:35 185 416K 341.40K
- 17857525.8 COMPLETED 10 1 2021-10-13T15:51:35 2021-10-13T15:51:35 2021-10-13T15:51:37 180 428K 317.40K
- 17857525.9 COMPLETED 10 1 2021-10-13T15:51:39 2021-10-13T15:51:39 2021-10-13T15:51:42 17 424K 272.70K
- 17857525.10 COMPLETED 10 1 2021-10-13T15:51:42 2021-10-13T15:51:42 2021-10-13T15:51:44 185 356K 304.20K
- 17857525.11 COMPLETED 10 1 2021-10-13T15:51:44 2021-10-13T15:51:44 2021-10-13T15:51:45 189 352K 322.20K
- 17857525.12 COMPLETED 10 1 2021-10-13T15:51:45 2021-10-13T15:51:45 2021-10-13T15:51:47 184 388K 310.70K
- 17857525.13 COMPLETED 10 1 2021-10-13T15:51:48 2021-10-13T15:51:48 2021-10-13T15:51:49 183 352K 336.90K
- 17857525.14 COMPLETED 10 1 2021-10-13T15:51:49 2021-10-13T15:51:49 2021-10-13T15:51:51 183 428K 346.60K
- 17857525.15 COMPLETED 10 1 2021-10-13T15:51:51 2021-10-13T15:51:51 2021-10-13T15:51:53 187 352K 335.90K
- 17857525.16 COMPLETED 10 1 2021-10-13T15:51:54 2021-10-13T15:51:54 2021-10-13T15:51:55 184 424K 270K
- 17857525.17 COMPLETED 10 1 2021-10-13T15:51:55 2021-10-13T15:51:55 2021-10-13T15:51:57 186 352K 304.80K
- 17857525.18 COMPLETED 10 1 2021-10-13T15:51:57 2021-10-13T15:51:57 2021-10-13T15:51:59 182 428K 357K
- 17857525.19 COMPLETED 10 1 2021-10-13T15:51:59 2021-10-13T15:51:59 2021-10-13T15:52:01 185 420K 280.60K
- 17857525.20 COMPLETED 10 1 2021-10-13T15:52:01 2021-10-13T15:52:01 2021-10-13T15:52:03 185 352K 339.90K
- 17857525.21 COMPLETED 10 1 2021-10-13T15:52:04 2021-10-13T15:52:04 2021-10-13T15:52:05 188 356K 340.20K
- 17857525.22 COMPLETED 10 1 2021-10-13T15:52:06 2021-10-13T15:52:06 2021-10-13T15:52:08 185 352K 287.50K
- 17857525.23 COMPLETED 10 1 2021-10-13T15:52:08 2021-10-13T15:52:08 2021-10-13T15:52:11 187 420K 349.40K
- 17857525.24 COMPLETED 10 1 2021-10-13T15:52:14 2021-10-13T15:52:14 2021-10-13T15:52:16 185 420K 353.70K
- 17857525.25 COMPLETED 10 1 2021-10-13T15:52:20 2021-10-13T15:52:20 2021-10-13T15:52:22 187 352K 340.30K
- 17857525.26 COMPLETED 10 1 2021-10-13T15:52:24 2021-10-13T15:52:24 2021-10-13T15:52:32 186 420K 345.80K
- 17857525.27 COMPLETED 10 1 2021-10-13T15:52:37 2021-10-13T15:52:37 2021-10-13T15:52:39 184 352K 341K
- 17857525.28 COMPLETED 10 1 2021-10-13T15:52:41 2021-10-13T15:52:41 2021-10-13T15:52:43 184 352K 326.20K
- 17857525.29 COMPLETED 10 1 2021-10-13T15:52:44 2021-10-13T15:52:44 2021-10-13T15:52:47 183 352K 319.30K
diff --git a/autosubmit_api/history/platform_monitor/platform_monitor.py b/autosubmit_api/history/platform_monitor/platform_monitor.py
deleted file mode 100644
index 46249781e0b739235df6e6d91c708ab1ee573d55..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/platform_monitor.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-from abc import ABCMeta, abstractmethod
-
-class PlatformMonitor(metaclass=ABCMeta):
- def __init__(self, platform_output):
- self.original_input = platform_output
- self.input = str(platform_output).strip()
-
-
- @abstractmethod
- def _identify_input_rows(self):
- """ """
-
diff --git a/autosubmit_api/history/platform_monitor/platform_utils.py b/autosubmit_api/history/platform_monitor/platform_utils.py
deleted file mode 100644
index dc5081678a62dfd0ddc4a32be8838879fd8ffc3c..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/platform_utils.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import os
-from time import mktime
-from datetime import datetime
-
-SLURM_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
-
-def parse_output_number(string_number):
- """
- Parses number in format 1.0K 1.0M 1.0G
-
- :param string_number: String representation of number
- :type string_number: str
- :return: number in float format
- :rtype: float
- """
- number = 0.0
- if (string_number):
- last_letter = string_number.strip()[-1]
- multiplier = 1.0
- if last_letter == "G":
- multiplier = 1000000000.0 # Billion
- number = float(string_number[:-1])
- elif last_letter == "M":
- multiplier = 1000000.0 # Million
- number = float(string_number[:-1])
- elif last_letter == "K":
- multiplier = 1000.0 # Thousand
- number = float(string_number[:-1])
- else:
- number = float(string_number)
- try:
- number = float(number) * multiplier
- except Exception as exp:
- number = 0.0
- pass
- return number
-
-def try_parse_time_to_timestamp(input):
- """
- Receives a string in format "%Y-%m-%dT%H:%M:%S" and tries to parse it to timestamp.
- """
- try:
- return int(mktime(datetime.strptime(input, SLURM_DATETIME_FORMAT).timetuple()))
- except:
- return 0
-
-def read_example(example_name):
- source_path = "autosubmit_api/history/platform_monitor/output_examples/"
- file_path = os.path.join(source_path, example_name)
- with open(file_path, "r") as fp:
- output_ssh = fp.read()
- return output_ssh
\ No newline at end of file
diff --git a/autosubmit_api/history/platform_monitor/slurm_monitor.py b/autosubmit_api/history/platform_monitor/slurm_monitor.py
deleted file mode 100644
index 30d7e4b7db6b48802ab3c1ad6924b28f4da09bb0..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/slurm_monitor.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-from .platform_monitor import PlatformMonitor
-from .slurm_monitor_item import SlurmMonitorItem
-
-class SlurmMonitor(PlatformMonitor):
- """ Manages Slurm commands interpretation. """
- def __init__(self, platform_output):
- super(SlurmMonitor, self).__init__(platform_output)
- self._identify_input_rows()
-
- @property
- def steps_energy(self):
- return sum([step.energy for step in self.input_items if step.is_step])
-
- @property
- def total_energy(self):
- return max(self.header.energy, self.steps_energy + self.extern.energy)
-
- @property
- def step_count(self):
- return len([step for step in self.input_items if step.is_step])
-
- def _identify_input_rows(self):
- lines = self.input.split("\n")
- self.input_items = [SlurmMonitorItem.from_line(line) for line in lines]
-
- @property
- def steps(self):
- return [item for item in self.input_items if item.is_step]
-
- @property
- def header(self):
- return next((header for header in self.input_items if header.is_header), None)
-
- @property
- def batch(self):
- return next((batch for batch in self.input_items if batch.is_batch), None)
-
- @property
- def extern(self):
- return next((extern for extern in self.input_items if extern.is_extern), None)
-
- def steps_plus_extern_approximate_header_energy(self):
- return abs(self.steps_energy + self.extern.energy - self.header.energy) <= 10
-
- def print_items(self):
- for item in self.input_items:
- print(item)
diff --git a/autosubmit_api/history/platform_monitor/slurm_monitor_item.py b/autosubmit_api/history/platform_monitor/slurm_monitor_item.py
deleted file mode 100644
index 65ed78758799d121b21e432352de4bae7d7f0bba..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/slurm_monitor_item.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-from . import platform_utils as utils
-
-class SlurmMonitorItem():
- def __init__(self, name, status, ncpus, nnodes, submit, start, finish, energy="0", MaxRSS=0.0, AveRSS=0.0):
- self.name = str(name)
- self.status = str(status)
- self.ncpus = int(ncpus)
- self.nnodes = int(nnodes)
- self.submit = utils.try_parse_time_to_timestamp(submit)
- self.start = utils.try_parse_time_to_timestamp(start)
- self.finish = utils.try_parse_time_to_timestamp(finish)
- self.energy_str = energy
- self.energy = utils.parse_output_number(energy)
- self.MaxRSS = utils.parse_output_number(MaxRSS)
- self.AveRSS = utils.parse_output_number(AveRSS)
-
- @property
- def is_header(self):
- return not self.is_detail
-
- @property
- def is_detail(self):
- if self.name.find(".") >= 0:
- return True
- return False
-
- @property
- def is_extern(self):
- if self.name.find(".ext") >= 0:
- return True
- return False
-
- @property
- def is_batch(self):
- if self.name.find(".bat") >= 0:
- return True
- return False
-
- @property
- def step_number(self):
- if self.is_step == True:
- point_loc = self.name.find(".")
- return int(self.name[point_loc+1:])
- return -1
-
- @property
- def is_step(self):
- if self.name.find(".") >= 0 and self.is_batch == False and self.is_extern == False:
- return True
- return False
-
- @classmethod
- def from_line(cls, line):
- line = line.strip().split()
- if len(line) < 2:
- raise Exception("Slurm parser found a line too short {0}".format(line))
- new_item = cls(line[0],
- line[1],
- str(line[2]) if len(line) > 2 else 0,
- str(line[3]) if len(line) > 3 else 0,
- str(line[4]) if len(line) > 4 else 0,
- str(line[5]) if len(line) > 5 else 0,
- str(line[6]) if len(line) > 6 else 0,
- str(line[7]) if len(line) > 7 else 0,
- str(line[8]) if len(line) > 8 else 0,
- str(line[9]) if len(line) > 9 else 0)
- return new_item
-
- def get_as_dict(self):
- return {"ncpus": self.ncpus,
- "nnodes": self.nnodes,
- "submit": self.submit,
- "start": self.start,
- "finish": self.finish,
- "energy": self.energy,
- "MaxRSS": self.MaxRSS,
- "AveRSS": self.AveRSS}
-
- def __str__(self):
- return "Name {0}, Status {1}, NCpus {2}, NNodes {3}, Submit {4}, Start {5}, Finish {6}, Energy {7}, MaxRSS {8}, AveRSS {9} [Energy Str {10}]".format(self.name, self.status, self.ncpus, self.nnodes, self.submit, self.start, self.finish, self.energy, self.MaxRSS, self.AveRSS, self.energy_str, self.is_batch)
\ No newline at end of file
diff --git a/autosubmit_api/history/platform_monitor/test.py b/autosubmit_api/history/platform_monitor/test.py
deleted file mode 100644
index 9ae89976c0559d519526b7700a8231258a1046ab..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/platform_monitor/test.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import unittest
-from autosubmit_api.history.platform_monitor import platform_utils as utils
-from autosubmit_api.history.platform_monitor.slurm_monitor import SlurmMonitor
-
-class TestSlurmMonitor(unittest.TestCase):
- def test_reader_on_simple_wrapper_example_1(self):
- ssh_output = utils.read_example("wrapper1.txt")
- slurm_monitor = SlurmMonitor(ssh_output)
- # Header
- self.assertTrue(slurm_monitor.input_items[0].is_batch == False)
- self.assertTrue(slurm_monitor.input_items[0].is_detail == False)
- self.assertTrue(slurm_monitor.input_items[0].is_extern == False)
- self.assertTrue(slurm_monitor.input_items[0].is_header == True)
- self.assertTrue(slurm_monitor.input_items[0].is_detail == False)
- # Batch
- self.assertTrue(slurm_monitor.input_items[1].is_batch == True)
- self.assertTrue(slurm_monitor.input_items[1].is_detail == True)
- self.assertTrue(slurm_monitor.input_items[1].is_extern == False)
- self.assertTrue(slurm_monitor.input_items[1].is_header == False)
- self.assertTrue(slurm_monitor.input_items[1].is_detail == True)
- # Extern
- self.assertTrue(slurm_monitor.input_items[2].is_batch == False)
- self.assertTrue(slurm_monitor.input_items[2].is_detail == True)
- self.assertTrue(slurm_monitor.input_items[2].is_extern == True)
- self.assertTrue(slurm_monitor.input_items[2].is_header == False)
- self.assertTrue(slurm_monitor.input_items[2].is_detail == True)
- header = slurm_monitor.header
- batch = slurm_monitor.batch
- extern = slurm_monitor.extern
- self.assertIsNotNone(header)
- self.assertIsNotNone(batch)
- self.assertIsNotNone(extern)
- # print("{0} {1} <- {2}".format(batch.name, batch.energy, batch.energy_str))
- # print("{0} {1} <- {2}".format(extern.name, extern.energy, extern.energy_str))
- # print("{0} {1} <- {2}".format(header.name, header.energy, header.energy_str))
- self.assertTrue(slurm_monitor.steps_plus_extern_approximate_header_energy())
-
-
- def test_reader_on_simple_wrapper_example_2(self):
- ssh_output = utils.read_example("wrapper2.txt") # not real
- slurm_monitor = SlurmMonitor(ssh_output)
- # Header
- self.assertTrue(slurm_monitor.input_items[0].is_batch == False)
- self.assertTrue(slurm_monitor.input_items[0].is_detail == False)
- self.assertTrue(slurm_monitor.input_items[0].is_step == False)
- self.assertTrue(slurm_monitor.input_items[0].is_extern == False)
- self.assertTrue(slurm_monitor.input_items[0].is_header == True)
- # Batch
- self.assertTrue(slurm_monitor.input_items[1].is_batch == True)
- self.assertTrue(slurm_monitor.input_items[1].is_detail == True)
- self.assertTrue(slurm_monitor.input_items[1].is_step == False)
- self.assertTrue(slurm_monitor.input_items[1].is_extern == False)
- self.assertTrue(slurm_monitor.input_items[1].is_header == False)
- # Step 0
- self.assertTrue(slurm_monitor.input_items[2].is_batch == False)
- self.assertTrue(slurm_monitor.input_items[2].is_detail == True)
- self.assertTrue(slurm_monitor.input_items[2].is_step == True)
- self.assertTrue(slurm_monitor.input_items[2].is_extern == False)
- self.assertTrue(slurm_monitor.input_items[2].is_header == False)
- self.assertTrue(slurm_monitor.input_items[2].step_number >= 0)
-
- def test_reader_on_big_wrapper(self):
- ssh_output = utils.read_example("wrapper_big.txt")
- slurm_monitor = SlurmMonitor(ssh_output)
- self.assertTrue(slurm_monitor.step_count == 30)
- header = slurm_monitor.header
- batch = slurm_monitor.batch
- extern = slurm_monitor.extern
- self.assertIsNotNone(header)
- self.assertIsNotNone(batch)
- self.assertIsNotNone(extern)
- self.assertTrue(slurm_monitor.steps_plus_extern_approximate_header_energy())
-
-
-if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/history/strategies.py b/autosubmit_api/history/strategies.py
deleted file mode 100644
index 8fdf88dabcb0a6f4031ac3b4c207863d82459a6d..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/strategies.py
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-from abc import ABCMeta, abstractmethod
-from .database_managers import database_models as Models
-import traceback
-from .internal_logging import Logging
-from .database_managers.database_manager import DEFAULT_LOCAL_ROOT_DIR, DEFAULT_HISTORICAL_LOGS_DIR
-
-class PlatformInformationHandler():
- def __init__(self, strategy):
- self._strategy = strategy
-
- @property
- def strategy(self):
- return self._strategy
-
- @strategy.setter
- def strategy(self, strategy):
- self._strategy = strategy
-
- def execute_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
- return self._strategy.apply_distribution(job_data_dc, job_data_dcs_in_wrapper, slurm_monitor)
-
-
-class Strategy(metaclass=ABCMeta):
- """ Strategy Interface """
-
- def __init__(self, historiclog_dir_path=DEFAULT_HISTORICAL_LOGS_DIR):
- self.historiclog_dir_path = historiclog_dir_path
-
- @abstractmethod
- def apply_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
- pass
-
- def set_job_data_dc_as_processed(self, job_data_dc, original_ssh_output):
- job_data_dc.platform_output = original_ssh_output
- job_data_dc.rowstatus = Models.RowStatus.PROCESSED
- return job_data_dc
-
- def set_job_data_dc_as_process_failed(self, job_data_dc, original_ssh_output):
- job_data_dc.platform_output = original_ssh_output
- job_data_dc.rowstatus = Models.RowStatus.FAULTY
- return job_data_dc
-
- def get_calculated_weights_of_jobs_in_wrapper(self, job_data_dcs_in_wrapper):
- """ Based on computational weight: running time in seconds * number of cpus. """
- total_weight = sum(job.computational_weight for job in job_data_dcs_in_wrapper)
- return {job.job_name: round(job.computational_weight/total_weight, 4) for job in job_data_dcs_in_wrapper}
-
-
-class SingleAssociationStrategy(Strategy):
-
- def __init__(self, historiclog_dir_path=DEFAULT_HISTORICAL_LOGS_DIR):
- super(SingleAssociationStrategy, self).__init__(historiclog_dir_path=historiclog_dir_path)
-
- def apply_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
- try:
- if len(job_data_dcs_in_wrapper) > 0:
- return []
- # job_data_dc.submit = slurm_monitor.header.submit
- # job_data_dc.start = slurm_monitor.header.start
- # job_data_dc.finish = slurm_monitor.header.finish
- job_data_dc.ncpus = slurm_monitor.header.ncpus
- job_data_dc.nnodes = slurm_monitor.header.nnodes
- job_data_dc.energy = slurm_monitor.header.energy
- job_data_dc.MaxRSS = max(slurm_monitor.header.MaxRSS, slurm_monitor.batch.MaxRSS if slurm_monitor.batch else 0, slurm_monitor.extern.MaxRSS if slurm_monitor.extern else 0) # TODO: Improve this rule
- job_data_dc.AveRSS = max(slurm_monitor.header.AveRSS, slurm_monitor.batch.AveRSS if slurm_monitor.batch else 0, slurm_monitor.extern.AveRSS if slurm_monitor.extern else 0)
- job_data_dc = self.set_job_data_dc_as_processed(job_data_dc, slurm_monitor.original_input)
- return [job_data_dc]
- except Exception as exp:
- Logging("strategies", self.historiclog_dir_path).log("SingleAssociationStrategy failed for {0}. Using ssh_output: {1}. Exception message: {2}".format(job_data_dc.job_name, slurm_monitor.original_input, str(exp)),
- traceback.format_exc())
- job_data_dc = self.set_job_data_dc_as_process_failed(job_data_dc, slurm_monitor.original_input)
- return [job_data_dc]
-
-class StraightWrapperAssociationStrategy(Strategy):
-
- def __init__(self, historiclog_dir_path=DEFAULT_HISTORICAL_LOGS_DIR):
- super(StraightWrapperAssociationStrategy, self).__init__(historiclog_dir_path=historiclog_dir_path)
-
- def apply_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
- """ """
- try:
- if len(job_data_dcs_in_wrapper) != slurm_monitor.step_count:
- return []
- result = []
- computational_weights = self.get_calculated_weights_of_jobs_in_wrapper(job_data_dcs_in_wrapper)
- for job_dc, step in zip(job_data_dcs_in_wrapper, slurm_monitor.steps):
- job_dc.energy = step.energy + computational_weights.get(job_dc.job_name, 0) * slurm_monitor.extern.energy
- job_dc.AveRSS = step.AveRSS
- job_dc.MaxRSS = step.MaxRSS
- job_dc.platform_output = ""
- if job_dc.job_name == job_data_dc.job_name:
- job_data_dc.energy = job_dc.energy
- job_data_dc.AveRSS = job_dc.AveRSS
- job_data_dc.MaxRSS = job_dc.MaxRSS
- result.append(job_dc)
- job_data_dc = self.set_job_data_dc_as_processed(job_data_dc, slurm_monitor.original_input)
- result.append(job_data_dc)
- return result
- except Exception as exp:
- Logging("strategies", self.historiclog_dir_path).log("StraightWrapperAssociationStrategy failed for {0}. Using ssh_output: {1}. Exception message: {2}".format(job_data_dc.job_name,
- slurm_monitor.original_input,
- str(exp)),
- traceback.format_exc())
- job_data_dc = self.set_job_data_dc_as_process_failed(job_data_dc, slurm_monitor.original_input)
- return [job_data_dc]
-
-class GeneralizedWrapperDistributionStrategy(Strategy):
-
- def __init__(self, historiclog_dir_path=DEFAULT_HISTORICAL_LOGS_DIR):
- super(GeneralizedWrapperDistributionStrategy, self).__init__(historiclog_dir_path=historiclog_dir_path)
-
- def apply_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
- try:
- result = []
- computational_weights = self.get_calculated_weights_of_jobs_in_wrapper(job_data_dcs_in_wrapper)
- for job_dc in job_data_dcs_in_wrapper:
- job_dc.energy = round(computational_weights.get(job_dc.job_name, 0) * slurm_monitor.total_energy,2)
- job_dc.platform_output = ""
- if job_dc.job_name == job_data_dc.job_name:
- job_data_dc.energy = job_dc.energy
- result.append(job_dc)
- job_data_dc = self.set_job_data_dc_as_processed(job_data_dc, slurm_monitor.original_input)
- result.append(job_data_dc)
- return result
- except Exception as exp:
- Logging("strategies", self.historiclog_dir_path).log("GeneralizedWrapperDistributionStrategy failed for {0}. Using ssh_output: {1}. Exception message: {2}".format(job_data_dc.job_name, slurm_monitor.original_input, str(exp)),
- traceback.format_exc())
- job_data_dc = self.set_job_data_dc_as_process_failed(job_data_dc, slurm_monitor.original_input)
- return [job_data_dc]
-
-class TwoDimWrapperDistributionStrategy(Strategy):
-
- def __init__(self, historiclog_dir_path=DEFAULT_HISTORICAL_LOGS_DIR):
- super(TwoDimWrapperDistributionStrategy, self).__init__(historiclog_dir_path=historiclog_dir_path)
-
- def apply_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
- try:
- result = []
- self.jobs_per_level = self.get_jobs_per_level(job_data_dcs_in_wrapper)
- if len(self.jobs_per_level) != slurm_monitor.step_count:
- return []
- comp_weight_per_level = self.get_comp_weight_per_level(self.jobs_per_level)
- level_energy = []
- for i, step in enumerate(slurm_monitor.steps):
- level_energy.append(step.energy + comp_weight_per_level[i] * slurm_monitor.extern.energy)
- for i, jobs in enumerate(self.jobs_per_level):
- weights = self.get_comp_weight_per_group_of_job_dcs(jobs)
- for j, job_dc in enumerate(jobs):
- job_dc.energy = round(level_energy[i] * weights[j], 2)
- if job_dc.job_name == job_data_dc.job_name:
- job_data_dc.energy = job_dc.energy
- result.append(job_dc)
- job_data_dc = self.set_job_data_dc_as_processed(job_data_dc, slurm_monitor.original_input)
- result.append(job_data_dc)
- return result
- except Exception as exp:
- Logging("strategies", self.historiclog_dir_path).log("TwoDimWrapperDistributionStrategy failed for {0}. Using ssh_output: {1}. Exception message: {2}".format(job_data_dc.job_name, slurm_monitor.original_input, str(exp)),
- traceback.format_exc())
- job_data_dc = self.set_job_data_dc_as_process_failed(job_data_dc, slurm_monitor.original_input)
- return [job_data_dc]
-
- def get_jobs_per_level(self, job_data_dcs_in_wrapper):
- """ List of Lists, index of list is the level. """
- job_name_to_object = {job.job_name: job for job in job_data_dcs_in_wrapper}
- levels = []
- roots_dcs = self._get_roots(job_data_dcs_in_wrapper)
- levels.append(roots_dcs)
- next_level = self.get_level(roots_dcs, job_name_to_object)
- while len(next_level) > 0:
- levels.append([job for job in next_level])
- next_level = self.get_level(next_level, job_name_to_object)
- return levels
-
- def _get_roots(self, job_data_dcs_in_wrapper):
- children_names = self._get_all_children(job_data_dcs_in_wrapper)
- return [job for job in job_data_dcs_in_wrapper if job.job_name not in children_names]
-
- def _get_all_children(self, job_data_dcs_in_wrapper):
- result = []
- for job_dc in job_data_dcs_in_wrapper:
- result.extend(job_dc.children_list)
- return result
-
- def get_comp_weight_per_group_of_job_dcs(self, jobs):
- total = sum(job.computational_weight for job in jobs)
- return [round(job.computational_weight/total, 4) for job in jobs]
-
- def get_comp_weight_per_level(self, jobs_per_level):
- level_weight = []
- total_weight = 0
- for jobs in jobs_per_level:
- computational_weight = sum(job.computational_weight for job in jobs)
- total_weight += computational_weight
- level_weight.append(computational_weight)
- return [round(weight/total_weight, 4) for weight in level_weight]
-
- def get_level(self, previous_level_dcs, job_name_to_object):
- children_names = []
- for job_dc in previous_level_dcs:
- children_names.extend(job_dc.children_list)
- level_dcs = [job_name_to_object[job_name] for job_name in children_names if job_name in job_name_to_object]
- return level_dcs
diff --git a/autosubmit_api/history/test.py b/autosubmit_api/history/test.py
deleted file mode 100644
index 9d0bbfdcc528c5b8d38a341c494c8d194218bb08..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/test.py
+++ /dev/null
@@ -1,367 +0,0 @@
-#!/usr/bin/python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import unittest
-import traceback
-import os
-import time
-import autosubmit_api.common.utils_for_testing as UtilsForTesting
-import autosubmit_api.performance.utils as PUtils
-from shutil import copy2
-from collections import namedtuple
-from autosubmit_api.history.internal_logging import Logging
-from autosubmit_api.history.strategies import StraightWrapperAssociationStrategy, GeneralizedWrapperDistributionStrategy, PlatformInformationHandler
-from autosubmit_api.config.basicConfig import APIBasicConfig
-from autosubmit_api.history.platform_monitor.slurm_monitor import SlurmMonitor
-from autosubmit_api.builders.experiment_history_builder import ExperimentHistoryDirector, ExperimentHistoryBuilder
-EXPID_TT00_SOURCE = "test_database.db~"
-EXPID_TT01_SOURCE = "test_database_no_run.db~"
-EXPID = "tt00"
-EXPID_NONE = "tt01"
-# BasicConfig.read()
-JOBDATA_DIR = APIBasicConfig.JOBDATA_DIR
-LOCAL_ROOT_DIR = APIBasicConfig.LOCAL_ROOT_DIR
-job = namedtuple("Job", ["name", "date", "member", "status_str", "children"])
-
-class TestExperimentHistory(unittest.TestCase):
- # @classmethod
- # def setUpClass(cls):
- # cls.exp = ExperimentHistory("tt00") # example database
- def setUp(self):
- APIBasicConfig.read()
- source_path_tt00 = os.path.join(JOBDATA_DIR, EXPID_TT00_SOURCE)
- self.target_path_tt00 = os.path.join(JOBDATA_DIR, "job_data_{0}.db".format(EXPID))
- copy2(source_path_tt00, self.target_path_tt00)
- source_path_tt01 = os.path.join(JOBDATA_DIR, EXPID_TT01_SOURCE)
- self.target_path_tt01 = os.path.join(JOBDATA_DIR, "job_data_{0}.db".format(EXPID_NONE))
- copy2(source_path_tt01, self.target_path_tt01)
- self.job_list = [
- job("a29z_20000101_fc2_1_POST", "2000-01-01 00:00:00", "POST", "COMPLETED", ""),
- job("a29z_20000101_fc1_1_CLEAN", "2000-01-01 00:00:00", "CLEAN", "COMPLETED", ""),
- job("a29z_20000101_fc3_1_POST", "2000-01-01 00:00:00", "POST", "RUNNING", ""),
- job("a29z_20000101_fc2_1_CLEAN", "2000-01-01 00:00:00", "CLEAN", "COMPLETED", ""),
- job("a29z_20000101_fc0_3_SIM", "2000-01-01 00:00:00", "SIM", "COMPLETED", ""),
- job("a29z_20000101_fc1_2_POST", "2000-01-01 00:00:00", "POST", "QUEUING", ""),
- ] # 2 differences, all COMPLETED
- self.job_list_large = [
- job("a29z_20000101_fc2_1_POST", "2000-01-01 00:00:00", "POST", "COMPLETED", ""),
- job("a29z_20000101_fc1_1_CLEAN", "2000-01-01 00:00:00", "CLEAN", "COMPLETED", ""),
- job("a29z_20000101_fc3_1_POST", "2000-01-01 00:00:00", "POST", "RUNNING", ""),
- job("a29z_20000101_fc2_1_CLEAN", "2000-01-01 00:00:00", "CLEAN", "COMPLETED", ""),
- job("a29z_20000101_fc0_3_SIM", "2000-01-01 00:00:00", "SIM", "COMPLETED", ""),
- job("a29z_20000101_fc1_2_POST", "2000-01-01 00:00:00", "POST", "QUEUING", ""),
- job("a29z_20000101_fc1_5_POST", "2000-01-01 00:00:00", "POST", "SUSPENDED", ""),
- job("a29z_20000101_fc1_4_POST", "2000-01-01 00:00:00", "POST", "FAILED", ""),
- job("a29z_20000101_fc2_5_CLEAN", "2000-01-01 00:00:00", "CLEAN", "SUBMITTED", ""),
- job("a29z_20000101_fc0_1_POST", "2000-01-01 00:00:00", "POST", "RUNNING", ""),
- ]
- # self.logging_tt00 = Logging("tt00", BasicConfig)
- # self.exp_db_manager_tt00 = ExperimentHistoryDbManager("tt00", BasicConfig)
- # self.exp_db_manager_tt00.initialize()
-
- def tearDown(self):
- os.remove(self.target_path_tt00)
- os.remove(self.target_path_tt01)
-
- def test_get_POST_jobs(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder("a28v")).build_reader_experiment_history(UtilsForTesting.get_mock_basic_config())
- completed_post_jobs = exp_history.manager.get_job_data_dcs_COMPLETED_by_section("POST")
- completed_sim_jobs = exp_history.manager.get_job_data_dcs_COMPLETED_by_section("SIM")
- self.assertTrue(len(completed_post_jobs) == 0)
- self.assertTrue(len(completed_sim_jobs) == 42)
-
- def test_get_job_data_dc_COMPLETED_by_wrapper_run_id(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder("a28v")).build_reader_experiment_history(UtilsForTesting.get_mock_basic_config())
- completed_jobs_by_wrapper_and_run_id = exp_history.manager.get_job_data_dc_COMPLETED_by_wrapper_run_id(1644619612435, 6)
- self.assertTrue(len(completed_jobs_by_wrapper_and_run_id) == 5)
-
- def test_queue_time_considering_wrapper_info(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder("a28v")).build_reader_experiment_history(UtilsForTesting.get_mock_basic_config())
- sim_job = exp_history.manager.get_job_data_dc_unique_latest_by_job_name("a28v_19500101_fc0_580_SIM")
- package_jobs = exp_history.manager.get_job_data_dc_COMPLETED_by_wrapper_run_id(sim_job.rowtype, sim_job.run_id)
- self.assertTrue(sim_job.queuing_time == 79244)
- # print("Adjusted queue time: {}".format(sim_job.queuing_time_considering_package(package_jobs)))
- self.assertTrue(sim_job.queuing_time_considering_package(package_jobs) == (79244 - (63179 + 16065)))
-
- def test_performance_metrics_historic(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder("a28v")).build_reader_experiment_history(UtilsForTesting.get_mock_basic_config())
- sim_job = exp_history.manager.get_job_data_dc_unique_latest_by_job_name("a28v_19500101_fc0_580_SIM")
- experiment_run = exp_history.manager.get_experiment_run_by_id(sim_job.run_id)
- package_jobs = exp_history.manager.get_job_data_dc_COMPLETED_by_wrapper_run_id(sim_job.rowtype, sim_job.run_id)
- calculated_ASYPD = PUtils.calculate_ASYPD_perjob(experiment_run.chunk_unit, experiment_run.chunk_size, sim_job.chunk, sim_job.queuing_time_considering_package(package_jobs) + sim_job.running_time, 0.0, sim_job.status_code)
- calculated_SYPD = PUtils.calculate_SYPD_perjob(experiment_run.chunk_unit, experiment_run.chunk_size, sim_job.chunk, sim_job.running_time, sim_job.status_code)
- # print("calculated ASYPD: {}".format(calculated_ASYPD))
- # print("calculated SYPD: {}".format(calculated_SYPD))
- self.assertTrue(calculated_ASYPD == round(((1.0/12.0) * 86400) / 15552, 2))
- self.assertTrue(calculated_SYPD == round(((1.0/12.0) * 86400) / 15552, 2))
-
- def test_get_historic(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder("a28v")).build_reader_experiment_history(UtilsForTesting.get_mock_basic_config())
- result = exp_history.get_historic_job_data("a28v_19500101_fc0_580_SIM")
- self.assertTrue(result[0]["ASYPD"] == 0.46)
- self.assertTrue(result[0]["SYPD"] == 0.46)
-
- def test_db_exists(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # # exp_history.initialize_database()
- self.assertTrue(exp_history.manager.my_database_exists() == True)
-
- def test_db_not_exists(self):
- # exp_history_db_manager = ExperimentHistoryDbManager("tt99", BasicConfig)
- # exp_history_logging = Logging("tt99", BasicConfig)
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder("tt99")).build_reader_experiment_history() # ExperimentHistory("tt99", BasicConfig, exp_history_db_manager, exp_history_logging)
- self.assertTrue(exp_history.manager.my_database_exists() == False)
-
- def test_is_header_ready(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- self.assertTrue(exp_history.is_header_ready() == True)
-
- def test_detect_differences_job_list(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- differences = exp_history.detect_changes_in_job_list(self.job_list)
- expected_differences = ["a29z_20000101_fc3_1_POST", "a29z_20000101_fc1_2_POST"]
- for job_dc in differences:
- self.assertTrue(job_dc.job_name in expected_differences)
- self.assertTrue(len(differences) == 2)
-
- def test_built_list_of_changes(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- built_differences = exp_history._get_built_list_of_changes(self.job_list)
- expected_ids_differences = [90, 101]
- for item in built_differences:
- self.assertTrue(item[3] in expected_ids_differences)
-
- def test_get_date_member_count(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- dm_count = exp_history._get_date_member_completed_count(self.job_list)
- self.assertTrue(dm_count > 0)
-
- def test_should_we_create_new_run(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- CHANGES_COUNT = 1
- TOTAL_COUNT = 6
- current_experiment_run_dc = exp_history.manager.get_experiment_run_dc_with_max_id()
- current_experiment_run_dc.total = TOTAL_COUNT
- should_we = exp_history.should_we_create_a_new_run(self.job_list, CHANGES_COUNT, current_experiment_run_dc, current_experiment_run_dc.chunk_unit, current_experiment_run_dc.chunk_size)
- self.assertTrue(should_we == False)
- TOTAL_COUNT_DIFF = 5
- current_experiment_run_dc.total = TOTAL_COUNT_DIFF
- should_we = exp_history.should_we_create_a_new_run(self.job_list, CHANGES_COUNT, current_experiment_run_dc, current_experiment_run_dc.chunk_unit, current_experiment_run_dc.chunk_size)
- self.assertTrue(should_we == True)
- CHANGES_COUNT = 5
- should_we = exp_history.should_we_create_a_new_run(self.job_list, CHANGES_COUNT, current_experiment_run_dc, current_experiment_run_dc.chunk_unit, current_experiment_run_dc.chunk_size)
- self.assertTrue(should_we == True)
- CHANGES_COUNT = 1
- current_experiment_run_dc.total = TOTAL_COUNT
- should_we = exp_history.should_we_create_a_new_run(self.job_list, CHANGES_COUNT, current_experiment_run_dc, current_experiment_run_dc.chunk_unit, current_experiment_run_dc.chunk_size*20)
- self.assertTrue(should_we == True)
- should_we = exp_history.should_we_create_a_new_run(self.job_list, CHANGES_COUNT, current_experiment_run_dc, current_experiment_run_dc.chunk_unit, current_experiment_run_dc.chunk_size)
- self.assertTrue(should_we == False)
- should_we = exp_history.should_we_create_a_new_run(self.job_list, CHANGES_COUNT, current_experiment_run_dc, "day", current_experiment_run_dc.chunk_size)
- self.assertTrue(should_we == True)
-
- def test_status_counts(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- result = exp_history.get_status_counts_from_job_list(self.job_list_large)
- self.assertTrue(result["COMPLETED"] == 4)
- self.assertTrue(result["QUEUING"] == 1)
- self.assertTrue(result["RUNNING"] == 2)
- self.assertTrue(result["FAILED"] == 1)
- self.assertTrue(result["SUSPENDED"] == 1)
- self.assertTrue(result["TOTAL"] == len(self.job_list_large))
-
- def test_create_new_experiment_run_with_counts(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- exp_run = exp_history.create_new_experiment_run(job_list=self.job_list)
- self.assertTrue(exp_run.chunk_size == 0)
- self.assertTrue(exp_run.chunk_unit == "NA")
- self.assertTrue(exp_run.total == len(self.job_list))
- self.assertTrue(exp_run.completed == 4)
-
- def test_finish_current_run(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- exp_run = exp_history.finish_current_experiment_run()
- self.assertTrue(len(exp_run.modified) > 0)
- self.assertTrue(exp_run.finish > 0)
-
- def test_process_job_list_changes(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- exp_run = exp_history.process_job_list_changes_to_experiment_totals(self.job_list)
- self.assertTrue(exp_run.total == len(self.job_list))
- self.assertTrue(exp_run.completed == 4)
- self.assertTrue(exp_run.running == 1)
- self.assertTrue(exp_run.queuing == 1)
-
- def test_calculated_weights(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- job_data_dcs = exp_history.manager.get_all_last_job_data_dcs()
- calculated_weights = GeneralizedWrapperDistributionStrategy().get_calculated_weights_of_jobs_in_wrapper(job_data_dcs)
- sum_comp_weight = 0
- for job_name in calculated_weights:
- sum_comp_weight += calculated_weights[job_name]
- self.assertTrue(abs(sum_comp_weight - 1) <= 0.01)
-
- def test_distribute_energy_in_wrapper_1_to_1(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- ssh_output = ''' 17857525 COMPLETED 10 1 2021-10-13T15:51:16 2021-10-13T15:51:17 2021-10-13T15:52:47 2.41K
- 17857525.batch COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 1.88K 6264K 6264K
- 17857525.extern COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 1.66K 473K 68K
- 17857525.0 COMPLETED 10 1 2021-10-13T15:51:21 2021-10-13T15:51:21 2021-10-13T15:51:22 186 352K 312.30K
- 17857525.1 COMPLETED 10 1 2021-10-13T15:51:23 2021-10-13T15:51:23 2021-10-13T15:51:24 186 420K 306.70K
- 17857525.2 COMPLETED 10 1 2021-10-13T15:51:24 2021-10-13T15:51:24 2021-10-13T15:51:27 188 352K 325.80K
- 17857525.3 COMPLETED 10 1 2021-10-13T15:51:28 2021-10-13T15:51:28 2021-10-13T15:51:29 192 352K 341.90K
- '''
- slurm_monitor = SlurmMonitor(ssh_output)
- job_data_dcs = exp_history.manager.get_all_last_job_data_dcs()[:4] # Get me 4 jobs
- weights = StraightWrapperAssociationStrategy().get_calculated_weights_of_jobs_in_wrapper(job_data_dcs)
- info_handler = PlatformInformationHandler(StraightWrapperAssociationStrategy())
- job_data_dcs_with_data = info_handler.execute_distribution(job_data_dcs[0], job_data_dcs, slurm_monitor)
- self.assertTrue(job_data_dcs_with_data[0].energy == round(slurm_monitor.steps[0].energy + weights[job_data_dcs_with_data[0].job_name]*slurm_monitor.extern.energy, 2))
- self.assertTrue(job_data_dcs_with_data[0].MaxRSS == slurm_monitor.steps[0].MaxRSS)
- self.assertTrue(job_data_dcs_with_data[2].energy == round(slurm_monitor.steps[2].energy + weights[job_data_dcs_with_data[2].job_name]*slurm_monitor.extern.energy, 2))
- self.assertTrue(job_data_dcs_with_data[2].AveRSS == slurm_monitor.steps[2].AveRSS)
-
- def test_distribute_energy_in_wrapper_general_case(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- ssh_output = ''' 17857525 COMPLETED 10 1 2021-10-13T15:51:16 2021-10-13T15:51:17 2021-10-13T15:52:47 2.41K
- 17857525.batch COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 1.88K 6264K 6264K
- 17857525.extern COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 1.66K 473K 68K
- 17857525.0 COMPLETED 10 1 2021-10-13T15:51:21 2021-10-13T15:51:21 2021-10-13T15:51:22 186 352K 312.30K
- 17857525.1 COMPLETED 10 1 2021-10-13T15:51:23 2021-10-13T15:51:23 2021-10-13T15:51:24 186 420K 306.70K
- 17857525.2 COMPLETED 10 1 2021-10-13T15:51:24 2021-10-13T15:51:24 2021-10-13T15:51:27 188 352K 325.80K
- 17857525.3 COMPLETED 10 1 2021-10-13T15:51:28 2021-10-13T15:51:28 2021-10-13T15:51:29 192 352K 341.90K
- '''
- slurm_monitor = SlurmMonitor(ssh_output)
- job_data_dcs = exp_history.manager.get_all_last_job_data_dcs()[:5] # Get me 5 jobs
- weights = GeneralizedWrapperDistributionStrategy().get_calculated_weights_of_jobs_in_wrapper(job_data_dcs)
- # print(sum(weights[k] for k in weights))
- info_handler = PlatformInformationHandler(GeneralizedWrapperDistributionStrategy())
- job_data_dcs_with_data = info_handler.execute_distribution(job_data_dcs[0], job_data_dcs, slurm_monitor)
- self.assertTrue(job_data_dcs_with_data[0].energy == round(slurm_monitor.total_energy * weights[job_data_dcs_with_data[0].job_name], 2))
- self.assertTrue(job_data_dcs_with_data[1].energy == round(slurm_monitor.total_energy * weights[job_data_dcs_with_data[1].job_name], 2))
- self.assertTrue(job_data_dcs_with_data[2].energy == round(slurm_monitor.total_energy * weights[job_data_dcs_with_data[2].job_name], 2))
- self.assertTrue(job_data_dcs_with_data[3].energy == round(slurm_monitor.total_energy * weights[job_data_dcs_with_data[3].job_name], 2))
- self.assertTrue(job_data_dcs_with_data[4].energy == round(slurm_monitor.total_energy * weights[job_data_dcs_with_data[4].job_name], 2))
- sum_energy = sum(job.energy for job in job_data_dcs_with_data[:5]) # Last 1 is original job_data_dc
- self.assertTrue(abs(sum_energy - slurm_monitor.total_energy) <= 10)
-
- def test_process_status_changes(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- CHUNK_UNIT = "month"
- CHUNK_SIZE = 20
- CURRENT_CONFIG = "CURRENT CONFIG"
- current_experiment_run_dc = exp_history.manager.get_experiment_run_dc_with_max_id()
- exp_run = exp_history.process_status_changes(job_list=self.job_list, chunk_unit=CHUNK_UNIT, chunk_size=CHUNK_SIZE, current_config=CURRENT_CONFIG) # Generates new run
- self.assertTrue(current_experiment_run_dc.run_id != exp_run.run_id)
- self.assertTrue(exp_run.chunk_unit == CHUNK_UNIT)
- self.assertTrue(exp_run.metadata == CURRENT_CONFIG)
- self.assertTrue(exp_run.total == len(self.job_list))
- current_experiment_run_dc = exp_history.manager.get_experiment_run_dc_with_max_id()
- exp_run = exp_history.process_status_changes(job_list=self.job_list, chunk_unit=CHUNK_UNIT, chunk_size=CHUNK_SIZE, current_config=CURRENT_CONFIG) # Same run
- self.assertTrue(current_experiment_run_dc.run_id == exp_run.run_id)
- new_job_list = [
- job("a29z_20000101_fc2_1_POST", "2000-01-01 00:00:00", "POST", "FAILED", ""),
- job("a29z_20000101_fc1_1_CLEAN", "2000-01-01 00:00:00", "CLEAN", "FAILED", ""),
- job("a29z_20000101_fc3_1_POST", "2000-01-01 00:00:00", "POST", "RUNNING", ""),
- job("a29z_20000101_fc2_1_CLEAN", "2000-01-01 00:00:00", "CLEAN", "FAILED", ""),
- job("a29z_20000101_fc0_3_SIM", "2000-01-01 00:00:00", "SIM", "FAILED", ""),
- job("a29z_20000101_fc1_2_POST", "2000-01-01 00:00:00", "POST", "QUEUING", ""),
- ]
- current_experiment_run_dc = exp_history.manager.get_experiment_run_dc_with_max_id()
- exp_run = exp_history.process_status_changes(job_list=new_job_list, chunk_unit=CHUNK_UNIT, chunk_size=CHUNK_SIZE, current_config=CURRENT_CONFIG) # Generates new run
- self.assertTrue(current_experiment_run_dc.run_id != exp_run.run_id)
- self.assertTrue(exp_run.total == len(new_job_list))
- self.assertTrue(exp_run.failed == 4)
-
- def test_write_submit_time(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # exp_history.initialize_database()
- JOB_NAME = "a29z_20000101_fc2_1_SIM"
- NCPUS = 128
- PLATFORM_NAME = "marenostrum4"
- JOB_ID = 101
- inserted_job_data_dc = exp_history.write_submit_time(JOB_NAME, time.time(), "SUBMITTED", NCPUS, "00:30", "debug", "20000101", "fc2", "SIM", 1, PLATFORM_NAME, JOB_ID, "bsc_es", 1, "")
- self.assertTrue(inserted_job_data_dc.job_name == JOB_NAME)
- self.assertTrue(inserted_job_data_dc.ncpus == NCPUS)
- self.assertTrue(inserted_job_data_dc.children == "")
- self.assertTrue(inserted_job_data_dc.energy == 0)
- self.assertTrue(inserted_job_data_dc.platform == PLATFORM_NAME)
- self.assertTrue(inserted_job_data_dc.job_id == JOB_ID)
- self.assertTrue(inserted_job_data_dc.qos == "debug")
-
-
- def test_write_start_time(self):
- exp_history = ExperimentHistoryDirector(ExperimentHistoryBuilder(EXPID)).build_current_experiment_history() # ExperimentHistory("tt00", BasicConfig, self.exp_db_manager_tt00, self.logging_tt00)
- # # exp_history.initialize_database()
- JOB_NAME = "a29z_20000101_fc2_1_SIM"
- NCPUS = 128
- PLATFORM_NAME = "marenostrum4"
- JOB_ID = 101
- inserted_job_data_dc_submit = exp_history.write_submit_time(JOB_NAME, time.time(), "SUBMITTED", NCPUS, "00:30", "debug", "20000101", "fc2", "SIM", 1, PLATFORM_NAME, JOB_ID, "bsc_es", 1, "")
- inserted_job_data_dc = exp_history.write_start_time(JOB_NAME, time.time(), "RUNNING", NCPUS, "00:30", "debug", "20000101", "fc2", "SIM", 1, PLATFORM_NAME, JOB_ID, "bsc_es", 1, "")
- self.assertTrue(inserted_job_data_dc.job_name == JOB_NAME)
- self.assertTrue(inserted_job_data_dc.ncpus == NCPUS)
- self.assertTrue(inserted_job_data_dc.children == "")
- self.assertTrue(inserted_job_data_dc.energy == 0)
- self.assertTrue(inserted_job_data_dc.platform == PLATFORM_NAME)
- self.assertTrue(inserted_job_data_dc.job_id == JOB_ID)
- self.assertTrue(inserted_job_data_dc.status == "RUNNING")
- self.assertTrue(inserted_job_data_dc.qos == "debug")
-
-
-
-class TestLogging(unittest.TestCase):
-
- def setUp(self):
- APIBasicConfig.read()
- message = "No Message"
- try:
- raise Exception("Setup test exception")
- except:
- message = traceback.format_exc()
- self.log = Logging("tt00", APIBasicConfig)
- self.exp_message = "Exception message"
- self.trace_message = message
-
- def test_build_message(self):
- message = self.log.build_message(self.exp_message, self.trace_message)
- # print(message)
- self.assertIsNotNone(message)
- self.assertTrue(len(message) > 0)
-
- def test_log(self):
- self.log.log(self.exp_message, self.trace_message)
-
-
-
-
-
-if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/history/test_job_history.py b/autosubmit_api/history/test_job_history.py
deleted file mode 100644
index 7e70bb3fd7d6d991d301ad5d72bda53fc490b274..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/test_job_history.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import unittest
-import autosubmit_api.common.utils_for_testing as TestUtils
-from autosubmit_api.builders.experiment_history_builder import ExperimentHistoryDirector, ExperimentHistoryBuilder
-
-class TestJobHistory(unittest.TestCase):
-
- def setUp(self):
- self.basic_config = TestUtils.get_mock_basic_config()
-
- def test_get_job_history_gets_correct_number_of_cases(self):
- # Arrange
- sut = ExperimentHistoryDirector(ExperimentHistoryBuilder("a3tb")).build_reader_experiment_history(self.basic_config)
- # Act
- job_history = sut.get_historic_job_data("a3tb_19930501_fc01_3_SIM")
- # Assert
- self.assertEqual(len(job_history), 5) # 5 rows in database
-
- def test_get_job_history_gets_correct_queuing_times(self):
- # Arrange
- sut = ExperimentHistoryDirector(ExperimentHistoryBuilder("a3tb")).build_reader_experiment_history(self.basic_config)
- # Act
- job_history = sut.get_historic_job_data("a3tb_19930501_fc01_3_SIM")
- counter_to_queue_timedelta_str = {int(job["counter"]): job["queue_time"] for job in job_history}
- # Assert
- self.assertEqual(counter_to_queue_timedelta_str[18], "0:03:28")
- self.assertEqual(counter_to_queue_timedelta_str[19], "0:00:04")
- self.assertEqual(counter_to_queue_timedelta_str[20], "0:00:05")
- self.assertEqual(counter_to_queue_timedelta_str[24], "0:01:18")
- self.assertEqual(counter_to_queue_timedelta_str[25], "0:02:35")
-
- def test_get_job_history_gets_correct_running_times(self):
- # Arrange
- sut = ExperimentHistoryDirector(ExperimentHistoryBuilder("a3tb")).build_reader_experiment_history(self.basic_config)
- # Act
- job_history = sut.get_historic_job_data("a3tb_19930501_fc01_3_SIM")
- counter_to_run_timedelta_str = {int(job["counter"]): job["run_time"] for job in job_history}
- # Assert
- self.assertEqual(counter_to_run_timedelta_str[18], "0:00:54")
- self.assertEqual(counter_to_run_timedelta_str[19], "0:00:53")
- self.assertEqual(counter_to_run_timedelta_str[20], "0:00:52")
- self.assertEqual(counter_to_run_timedelta_str[24], "0:23:16")
- self.assertEqual(counter_to_run_timedelta_str[25], "0:07:58")
-
- def test_get_job_history_gets_correct_SYPD(self):
- # Arrange
- sut = ExperimentHistoryDirector(ExperimentHistoryBuilder("a3tb")).build_reader_experiment_history(self.basic_config)
- # Act
- job_history = sut.get_historic_job_data("a3tb_19930501_fc01_3_SIM")
- counter_to_SYPD = {int(job["counter"]): job["SYPD"] for job in job_history}
- # Assert
- self.assertEqual(counter_to_SYPD[18], None)
- self.assertEqual(counter_to_SYPD[19], None)
- self.assertEqual(counter_to_SYPD[20], None)
- self.assertEqual(counter_to_SYPD[24], round(86400*(1.0/12.0)/1396, 2))
- self.assertEqual(counter_to_SYPD[25], round(86400*(1.0/12.0)/478, 2))
-
- def test_get_job_history_gets_correct_ASYPD(self):
- # Arrange
- sut = ExperimentHistoryDirector(ExperimentHistoryBuilder("a3tb")).build_reader_experiment_history(self.basic_config)
- # Act
- job_history = sut.get_historic_job_data("a3tb_19930501_fc01_3_SIM")
- counter_to_ASYPD = {int(job["counter"]): job["ASYPD"] for job in job_history}
- # Assert
- self.assertEqual(counter_to_ASYPD[18], None)
- self.assertEqual(counter_to_ASYPD[19], None)
- self.assertEqual(counter_to_ASYPD[20], None)
- self.assertEqual(counter_to_ASYPD[24], round(86400*(1.0/12.0)/(1396 + 78), 2))
- self.assertEqual(counter_to_ASYPD[25], round(86400*(1.0/12.0)/(478 + 155), 2))
-
-if __name__ == "__main__":
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/history/test_strategies.py b/autosubmit_api/history/test_strategies.py
deleted file mode 100644
index 04d7fa870d50f54dc884df2020df489adbd1bf12..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/test_strategies.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/python
-
-# Copyright 2015-2020 Earth Sciences Department, BSC-CNS
-# This file is part of Autosubmit.
-
-# Autosubmit is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Autosubmit is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Autosubmit. If not, see .
-
-import unittest
-from collections import namedtuple
-from autosubmit_api.history.data_classes.job_data import JobData
-from autosubmit_api.history.strategies import StraightWrapperAssociationStrategy, GeneralizedWrapperDistributionStrategy, PlatformInformationHandler, TwoDimWrapperDistributionStrategy
-from autosubmit_api.history.platform_monitor.slurm_monitor import SlurmMonitor
-job_dc = namedtuple("Job", ["job_name", "date", "member", "status_str", "children", "children_list"])
-
-class Test2DWrapperDistributionStrategy(unittest.TestCase):
- def setUp(self):
- self.strategy = TwoDimWrapperDistributionStrategy()
- self.job_data_dcs_in_wrapper = [
- JobData(0, job_name="a29z_20000101_fc2_1_POSTR", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc1_1_CLEAN, a29z_20000101_fc3_1_POST"),
- JobData(0, job_name="a29z_20000101_fc1_1_CLEAN", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc2_1_CLEAN"),
- JobData(0, job_name="a29z_20000101_fc3_1_POST", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc0_3_SIM"),
- JobData(0, job_name="a29z_20000101_fc2_1_CLEAN", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children=""),
- JobData(0, job_name="a29z_20000101_fc0_3_SIM", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children=""),
- JobData(0, job_name="a29z_20000101_fc1_2_POSTR1", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc1_5_POST2"),
- JobData(0, job_name="a29z_20000101_fc1_5_POST2", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc1_4_POST3"),
- JobData(0, job_name="a29z_20000101_fc1_4_POST3", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc2_5_CLEAN4"),
- JobData(0, job_name="a29z_20000101_fc2_5_CLEAN4", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children="a29z_20000101_fc0_1_POST5"),
- JobData(0, job_name="a29z_20000101_fc0_1_POST5", status="COMPLETED", submit=10, start=100, finish=200, ncpus=100, energy=0, children=""),
- ]
-
- def test_get_all_children(self):
- children = self.strategy._get_all_children(self.job_data_dcs_in_wrapper)
- self.assertTrue(len(children) == 8)
-
- def test_get_roots(self):
- roots = self.strategy._get_roots(self.job_data_dcs_in_wrapper)
- self.assertTrue(len(roots) == 2)
-
- def test_get_level(self):
- roots = self.strategy._get_roots(self.job_data_dcs_in_wrapper)
- job_name_to_children_names = {job.job_name: job.children_list for job in self.job_data_dcs_in_wrapper}
- next_level = self.strategy.get_level(roots, job_name_to_children_names)
- self.assertTrue(len(next_level) == 3)
-
- def test_get_jobs_per_level(self):
- levels = self.strategy.get_jobs_per_level(self.job_data_dcs_in_wrapper)
- for level in levels:
- print([job.job_name for job in level])
- self.assertTrue(len(levels) == 5)
- self.assertTrue("a29z_20000101_fc0_1_POST5" in [job.job_name for job in levels[4]])
-
- def test_energy_distribution(self):
- ssh_output = ''' 17857525 COMPLETED 10 1 2021-10-13T15:51:16 2021-10-13T15:51:17 2021-10-13T15:52:47 2.62K
- 17857525.batch COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 1.88K 6264K 6264K
- 17857525.extern COMPLETED 10 1 2021-10-13T15:51:17 2021-10-13T15:51:17 2021-10-13T15:52:47 1.66K 473K 68K
- 17857525.0 COMPLETED 10 1 2021-10-13T15:51:21 2021-10-13T15:51:21 2021-10-13T15:51:22 186 352K 312.30K
- 17857525.1 COMPLETED 10 1 2021-10-13T15:51:23 2021-10-13T15:51:23 2021-10-13T15:51:24 186 420K 306.70K
- 17857525.2 COMPLETED 10 1 2021-10-13T15:51:24 2021-10-13T15:51:24 2021-10-13T15:51:27 188 352K 325.80K
- 17857525.3 COMPLETED 10 1 2021-10-13T15:51:28 2021-10-13T15:51:28 2021-10-13T15:51:29 192 352K 341.90K
- 17857525.4 COMPLETED 10 1 2021-10-13T15:51:28 2021-10-13T15:51:28 2021-10-13T15:51:29 210 352K 341.90K
- '''
- slurm_monitor = SlurmMonitor(ssh_output)
- info_handler = PlatformInformationHandler(TwoDimWrapperDistributionStrategy())
- job_dcs = info_handler.execute_distribution(self.job_data_dcs_in_wrapper[0], self.job_data_dcs_in_wrapper, slurm_monitor)
- for job in job_dcs:
- print(("{0} -> {1} and {2} : ncpus {3} running {4}".format(job.job_name, job.energy, job.rowstatus, job.ncpus, job.running_time)))
- for level in info_handler.strategy.jobs_per_level:
- print([job.job_name for job in level])
- total_in_jobs = sum(job.energy for job in job_dcs[:-1]) # ignore last
- self.assertTrue(abs(total_in_jobs - slurm_monitor.total_energy) <= 10)
- self.assertTrue(abs(job_dcs[0].energy - 259) < 1)
- self.assertTrue(abs(job_dcs[1].energy - 259) < 1)
- self.assertTrue(abs(job_dcs[2].energy - 228) < 1)
- self.assertTrue(abs(job_dcs[3].energy - 228) < 1)
- self.assertTrue(abs(job_dcs[4].energy - 228) < 1)
- self.assertTrue(abs(job_dcs[5].energy - 228.67) < 1)
- self.assertTrue(abs(job_dcs[6].energy - 228.67) < 1)
- self.assertTrue(abs(job_dcs[7].energy - 228.67) < 1)
- self.assertTrue(abs(job_dcs[8].energy - 358) < 1)
- self.assertTrue(abs(job_dcs[9].energy - 376) < 1)
-
-if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/history/test_utils.py b/autosubmit_api/history/test_utils.py
deleted file mode 100644
index 7abcc510b6a014f031571eb911a945d467deb50b..0000000000000000000000000000000000000000
--- a/autosubmit_api/history/test_utils.py
+++ /dev/null
@@ -1,32 +0,0 @@
-
-import unittest
-import history.utils as HUtils
-
-class TestUtils(unittest.TestCase):
- def setUp(self):
- pass
-
- def test_generate_arguments(self):
- arguments = {"status": 4, "last": 1, "rowtype": 2}
- statement, arg_values = HUtils.get_built_statement_from_kwargs("id", status=4, last=1, rowtype=2)
- print(statement)
- print(arg_values)
- self.assertTrue(statement.find("status") >= 0)
- self.assertTrue(statement.find("last") >= 0)
- self.assertTrue(statement.find("rowtype") >= 0)
- self.assertTrue(statement.find(" ORDER BY id") >= 0)
-
- def test_generate_arguments_kwargs(self):
- def inner_call(expid, **kwargs):
- return HUtils.get_built_statement_from_kwargs("created", **kwargs)
- arguments = {"status": 4, "last": 1, "rowtype": 2}
- answer, arg_values = inner_call("a28v", **arguments)
- print(answer)
- print(arg_values)
- self.assertTrue(answer.find("status") >= 0)
- self.assertTrue(answer.find("last") >= 0)
- self.assertTrue(answer.find("rowtype") >= 0)
- self.assertTrue(answer.find(" ORDER BY created") >= 0)
-
-if __name__ == "__main__":
- unittest.main()
\ No newline at end of file
diff --git a/tests/experiments/calculation_in_progress.lock b/autosubmit_api/persistance/__init__.py
similarity index 100%
rename from tests/experiments/calculation_in_progress.lock
rename to autosubmit_api/persistance/__init__.py
diff --git a/autosubmit_api/persistance/experiment.py b/autosubmit_api/persistance/experiment.py
new file mode 100644
index 0000000000000000000000000000000000000000..178c1b0e83421854bdaf763d62095fd99a1eac75
--- /dev/null
+++ b/autosubmit_api/persistance/experiment.py
@@ -0,0 +1,66 @@
+import os
+from autosubmit_api.config.basicConfig import APIBasicConfig
+
+
+class ExperimentPaths:
+ """
+ Helper class that builds related directories/files paths of an experiment
+ """
+
+ def __init__(self, expid: str) -> None:
+ self._expid = expid
+
+ @property
+ def expid(self):
+ return self._expid
+
+ @property
+ def exp_dir(self):
+ return os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, self.expid)
+
+ @property
+ def pkl_dir(self):
+ return os.path.join(self.exp_dir, "pkl")
+
+ @property
+ def job_list_pkl(self):
+ filename = f"job_list_{self.expid}.pkl"
+ return os.path.join(self.pkl_dir, filename)
+
+ @property
+ def job_packages_db(self):
+ filename = f"job_packages_{self.expid}.db"
+ return os.path.join(self.pkl_dir, filename)
+
+ @property
+ def tmp_dir(self):
+ """
+ tmp dir
+ """
+ return os.path.join(self.exp_dir, APIBasicConfig.LOCAL_TMP_DIR)
+
+ @property
+ def tmp_log_dir(self):
+ """
+ tmp/LOG_{expid} dir
+ """
+ return os.path.join(self.tmp_dir, f"LOG_{self.expid}")
+
+ @property
+ def tmp_as_logs_dir(self):
+ """
+ tmp/ASLOGS dir
+ """
+ return os.path.join(self.tmp_dir, APIBasicConfig.LOCAL_ASLOG_DIR)
+
+ @property
+ def job_data_db(self):
+ return os.path.join(APIBasicConfig.JOBDATA_DIR, f"job_data_{self.expid}.db")
+
+ @property
+ def structure_db(self):
+ return os.path.join(APIBasicConfig.STRUCTURES_DIR, f"structure_{self.expid}.db")
+
+ @property
+ def graph_data_db(self):
+ return os.path.join(APIBasicConfig.GRAPHDATA_DIR, f"graph_data_{self.expid}.db")
diff --git a/autosubmit_api/views/v4.py b/autosubmit_api/views/v4.py
index 07db10cfd3995f788b4154fb8ef5b1a8936d38ae..f42b77e44fb02fe4f0f9182095865bb80dfdbd5d 100644
--- a/autosubmit_api/views/v4.py
+++ b/autosubmit_api/views/v4.py
@@ -165,8 +165,8 @@ class ExperimentView(MethodView):
# Get current run data from history
last_modified_timestamp = exp.created
- completed = exp.completed_jobs if exp.completed_jobs else 0
- total = exp.total_jobs if exp.total_jobs else 0
+ completed = 0
+ total = 0
submitted = 0
queuing = 0
running = 0
@@ -181,10 +181,6 @@ class ExperimentView(MethodView):
if (
current_run
and current_run.total > 0
- and (
- current_run.total == total
- or current_run.modified_timestamp > last_modified_timestamp
- )
):
completed = current_run.completed
total = current_run.total
diff --git a/autosubmit_api/workers/business/populate_times.py b/autosubmit_api/workers/business/populate_times.py
deleted file mode 100644
index 5c5e306d4cfb281f3f7a769dfd74775040e7de23..0000000000000000000000000000000000000000
--- a/autosubmit_api/workers/business/populate_times.py
+++ /dev/null
@@ -1,365 +0,0 @@
-import datetime
-import os
-import pwd
-import time
-import subprocess
-import traceback
-import socket
-import pickle
-from autosubmit_api.builders.configuration_facade_builder import AutosubmitConfigurationFacadeBuilder, ConfigurationFacadeDirector
-from autosubmit_api.components.experiment.pkl_organizer import PklOrganizer
-from autosubmit_api.components.jobs.utils import get_job_total_stats
-from autosubmit_api.config.config_common import AutosubmitConfigResolver
-from autosubmit_api.experiment import common_db_requests as DbRequests
-from autosubmit_api.config.basicConfig import APIBasicConfig
-from autosubmit_api.common.utils import Status
-from bscearth.utils.config_parser import ConfigParserFactory
-
-
-SAFE_TIME_LIMIT = 300
-
-
-def process_completed_times(time_condition=60):
- """
- Tests for completed jobs of all autosubmit experiments and updates their completion times data in job_times and experiment_times.
- :param time_condition: Time difference in seconds that qualifies a experiment as out of date.
- :type time_condition: Integer
- """
- try:
- t0 = time.time()
- DEBUG = False
- APIBasicConfig.read()
- path = APIBasicConfig.LOCAL_ROOT_DIR
- # Time test for data retrieval
- # All experiment from file system
- currentDirectories = subprocess.Popen(['ls', '-t', path],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT) if (os.path.exists(path)) else None
- stdOut, _ = currentDirectories.communicate() if currentDirectories else (None, None)
- # Building connection to ecearth
-
- current_table = DbRequests.prepare_completed_times_db()
- # Build list of all folder in /esarchive/autosubmit which should be considered as experiments (although some might not be)
- # Pre process
- _preprocess_completed_times()
- experiments = stdOut.split() if stdOut else []
- counter = 0
-
- # Get current `details` from ecearth.db and convert to set for effcient contain test
- details_table_ids_set = set(DbRequests.get_exps_detailed_complete().keys())
- # Get current `experiments` from ecearth.db
- experiments_table = DbRequests.get_exps_base()
-
- for expid in experiments:
- # Experiment names should be 4 char long
- if (len(expid) != 4):
- counter += 1
- continue
- # Experiment names must correspond to an experiment that contains a .pkl file
- exp_str = expid.decode("UTF-8")
- full_path = os.path.join(path, exp_str, "pkl", "job_list_" + exp_str + ".pkl")
- timest = 0
- if os.path.exists(full_path):
- # get time of last modification
- timest = int(os.stat(full_path).st_mtime)
- else:
- counter += 1
- continue
- counter += 1
- experiments_table_exp_id = experiments_table.get(exp_str, None)
- if current_table.get(exp_str, None) is None:
- # Pkl exists but is not registered in the table
- # INSERT
- # print("Pkl of " + exp_str + " exists but not in the table: INSERT")
- current_id = _process_pkl_insert_times(exp_str, full_path, timest, APIBasicConfig, DEBUG)
- _process_details_insert_or_update(exp_str, experiments_table_exp_id, experiments_table_exp_id in details_table_ids_set)
- else:
- exp_id, created, modified, total_jobs, completed_jobs = current_table[exp_str]
- time_diff = int(timest - modified)
- # print("Pkl of " + exp_str + " exists")
- current_id = _process_pkl_insert_times(exp_str, full_path, timest, APIBasicConfig, DEBUG)
- if time_diff > time_condition:
- # Update table
- _process_pkl_update_times(exp_str, full_path, timest, APIBasicConfig, exp_id, DEBUG)
- _process_details_insert_or_update(exp_str, experiments_table_exp_id, experiments_table_exp_id in details_table_ids_set)
- DbRequests.update_experiment_times_only_modified(exp_id, timest)
- t1 = time.time()
- # Timer safeguard
- if (t1 - t0) > SAFE_TIME_LIMIT:
- raise Exception(
- "Time limit reached {0:06.2f} seconds on process_completed_times while processing {1}. Time spent on reading data {2:06.2f} seconds.".format((t1 - t0), exp_str, (t1 - t0)))
- except Exception as ex:
- print((traceback.format_exc()))
- print(str(ex))
-
-
-def _describe_experiment(experiment_id):
- user = ""
- created = ""
- model = ""
- branch = ""
- hpc = ""
- APIBasicConfig.read()
- exp_path = os.path.join(APIBasicConfig.LOCAL_ROOT_DIR, experiment_id)
- if not os.path.exists(exp_path):
- return user, created, model, branch, hpc
-
- user = os.stat(exp_path).st_uid
- try:
- user = pwd.getpwuid(user).pw_name
- except:
- pass
-
- created = datetime.datetime.fromtimestamp(
- os.path.getmtime(exp_path))
-
- try:
- as_conf = AutosubmitConfigResolver(
- experiment_id, APIBasicConfig, ConfigParserFactory())
- as_conf.reload()
-
- project_type = as_conf.get_project_type()
- if project_type != "none":
- if not as_conf.check_proj():
- return False
- if (as_conf.get_svn_project_url()):
- model = as_conf.get_svn_project_url()
- branch = as_conf.get_svn_project_url()
- else:
- model = as_conf.get_git_project_origin()
- branch = as_conf.get_git_project_branch()
- if model is "":
- model = "Not Found"
- if branch is "":
- branch = "Not Found"
- hpc = as_conf.get_platform()
- except:
- pass
- return user, created, model, branch, hpc
-
-
-def _process_details_insert_or_update(expid, exp_id, current_details):
- """
- Decides whether the experiment should be inserted or updated in the details table.
- :param expid: name of experiment e.g: a001
- :type expid: str
- :param exp_id: id of experiment e.g: 1
- :type exp_id: int
- :param current_details: True if it exp_id exists in details table, False otherwise
- :rtype: bool
- :result: True if successful, False otherwise
- :rtype: bool
- """
- result = False
- if exp_id:
- try:
- user, created, model, branch, hpc = _describe_experiment(expid)
- if current_details:
- # Update
- result = DbRequests._update_ecearth_details(exp_id, user, created, model, branch, hpc)
- else:
- # Insert
- _Id = DbRequests._insert_into_ecearth_details(exp_id, user, created, model, branch, hpc)
- result = True if _Id else False
- except Exception as exp:
- print(exp)
- return result
-
-
-def _preprocess_completed_times():
- """
- Preprocess table to get rid of possible conflicts
- :param current_table: table experiment_times from as_times.db
- """
- current_table = DbRequests.get_experiment_times_group()
- for name, _ids in list(current_table.items()):
- if len(_ids) > 1:
- print((str(name) + " has more than 1 register."))
- for i in range(0, len(_ids) - 1):
- _id = _ids[i]
- deleted_outdated = DbRequests.delete_experiment_data(_id)
-
-def _process_pkl_update_times(expid, path_pkl, timest_pkl, BasicConfig, exp_id, debug=False):
- """
- Updates register in job_times and experiment_times for the given experiment.
- :param expid: Experiment name
- :type expid: String
- :param path_pkl: path to the pkl file (DEPRECATED)
- :type path_pkl: String
- :param timest_pkl: Timestamp of the last modified date of the pkl file
- :type timest_pkl: Integer
- :param BasicConfig: Configuration of AS
- :type BasicConfig: Object
- :param exp_id: Id of experiment
- :type exp_id: Integer
- :param debug: Flag (testing purposes)
- :type debug: Boolean
- :return: Nothing
- """
- # debug = True
- try:
- found_in_pkl = list()
- BasicConfig.read()
-
- tmp_path = os.path.join(BasicConfig.LOCAL_ROOT_DIR, expid, BasicConfig.LOCAL_TMP_DIR)
- job_times_db = dict()
- total_jobs = 0
- completed_jobs = 0
- fd = None
- # t_start = time.time()
- # Get current detail from database
- experiment_times_detail = DbRequests.get_times_detail(exp_id)
- # t_seconds = time.time() - t_start
- must_update_header = False
-
- to_update = []
- to_create = []
-
- # Read PKL
- autosubmit_config_facade = ConfigurationFacadeDirector(
- AutosubmitConfigurationFacadeBuilder(expid)).build_autosubmit_configuration_facade()
- pkl_organizer = PklOrganizer(autosubmit_config_facade)
- for job_item in pkl_organizer.current_content:
- total_jobs += 1
- status_code = job_item.status
- job_name = job_item.name
- found_in_pkl.append(job_name)
- status_text = str(Status.VALUE_TO_KEY[status_code])
- if (status_code == Status.COMPLETED):
- completed_jobs += 1
- if (experiment_times_detail) and job_name in list(experiment_times_detail.keys()):
- # If job in pkl exists in database, retrieve data from database
- submit_time, start_time, finish_time, status_text_in_table, detail_id = experiment_times_detail[job_name]
- if (status_text_in_table != status_text):
- # If status has changed
- submit_time, start_time, finish_time, _ = get_job_total_stats(status_code, job_name, tmp_path)
- submit_ts = int(time.mktime(submit_time.timetuple())) if len(str(submit_time)) > 0 else 0
- start_ts = int(time.mktime(start_time.timetuple())) if len(str(start_time)) > 0 else 0
- finish_ts = int(time.mktime(finish_time.timetuple())) if len(str(finish_time)) > 0 else 0
- # UPDATE
- must_update_header = True
- to_update.append((int(timest_pkl),
- submit_ts,
- start_ts,
- finish_ts,
- status_text,
- detail_id))
-
- else:
- # Insert only if it is not WAITING nor READY
- if (status_code not in [Status.WAITING, Status.READY]):
- submit_time, start_time, finish_time, status_text = get_job_total_stats(status_code, job_name, tmp_path)
- must_update_header = True
- to_create.append((exp_id,
- job_name,
- int(timest_pkl),
- int(timest_pkl),
- int(time.mktime(submit_time.timetuple())) if len(str(submit_time)) > 0 else 0,
- int(time.mktime(start_time.timetuple())) if len(str(start_time)) > 0 else 0,
- int(time.mktime(finish_time.timetuple())) if len(str(finish_time)) > 0 else 0,
- status_text))
-
-
- # Update Many
- if len(to_update) > 0:
- DbRequests.update_many_job_times(to_update)
- # Create Many
- if len(to_create) > 0:
- DbRequests.create_many_job_times(to_create)
-
- if must_update_header == True:
- exp_id = DbRequests.update_experiment_times(exp_id, int(timest_pkl), completed_jobs, total_jobs, debug)
-
- # Reviewing for deletes:
- if len(found_in_pkl) > 0 and (experiment_times_detail):
- detail_list = []
- for key in experiment_times_detail:
- if key not in found_in_pkl:
- # Delete Row
- submit_time, start_time, finish_time, status_text_in_table, detail_id = experiment_times_detail[key]
- detail_list.append((detail_id,))
- if len(detail_list) > 0:
- DbRequests._delete_many_from_job_times_detail(detail_list)
-
- except (socket.error, EOFError):
- # print(str(expid) + "\t EOF Error")
- pass
- except Exception as ex:
- print(expid)
- print((traceback.format_exc()))
-
-
-def _process_pkl_insert_times(expid, path_pkl, timest_pkl, BasicConfig, debug=False):
- """
- Process Pkl contents and insert information into database if status of jobs is not WAITING (to save space).
- :param conn: Connection to database
- :type conn: Sqlite3 connection object
- :param expid: Experiment name
- :type expid: String
- :param path_pkl: Path to the pkl file
- :type path_pkl: String
- :param timest_pkl: Timestamp of the pkl modified date
- :type timest_pkl: Integer
- :param BasicConfig: Configuration data of AS
- :type BasicConfig: Object
- :param debug: Flag (proper name should be test)
- :type debug: Boolean
- """
- # BasicConfig.read()
- # path = BasicConfig.LOCAL_ROOT_DIR
- # db_file = os.path.join(path, DbRequests.DB_FILE_AS_TIMES)
- # Build tmp path to search for TOTAL_STATS files
- tmp_path = os.path.join(BasicConfig.LOCAL_ROOT_DIR, expid, BasicConfig.LOCAL_TMP_DIR)
- job_times = dict() # Key: job_name
- total_jobs = 0
- completed_jobs = 0
- status_code = Status.UNKNOWN
- status_text = str(Status.VALUE_TO_KEY[status_code])
- try:
- # Read PKL
- autosubmit_config_facade = ConfigurationFacadeDirector(
- AutosubmitConfigurationFacadeBuilder(expid)).build_autosubmit_configuration_facade()
- pkl_organizer = PklOrganizer(autosubmit_config_facade)
- for job_item in pkl_organizer.current_content:
- total_jobs += 1
- status_code = job_item.status
- job_name = job_item.name
- status_text = str(Status.VALUE_TO_KEY[status_code])
- if (status_code == Status.COMPLETED):
- completed_jobs += 1
- job_times[job_name] = status_code
- except:
- pass
-
- try:
- # Insert header
- current_id = DbRequests.insert_experiment_times_header(expid, int(timest_pkl), total_jobs, completed_jobs, debug)
- if(current_id > 0):
- # Insert detail
- to_insert_many = []
- for job_name in job_times:
- # Inserting detail. Do not insert WAITING or READY jobs.
- status_code = job_times[job_name]
- if (status_code not in [Status.WAITING, Status.READY]):
- submit_time, start_time, finish_time, status_text = get_job_total_stats(status_code, job_name, tmp_path)
- to_insert_many.append((current_id,
- job_name,
- int(timest_pkl),
- int(timest_pkl),
- int(time.mktime(submit_time.timetuple())) if len(
- str(submit_time)) > 0 else 0,
- int(time.mktime(start_time.timetuple())) if len(
- str(start_time)) > 0 else 0,
- int(time.mktime(finish_time.timetuple())) if len(
- str(finish_time)) > 0 else 0,
- status_text))
- if len(to_insert_many) > 0:
- DbRequests.create_many_job_times(to_insert_many)
- else:
- pass
-
- return current_id
- except Exception as ex:
- print(expid)
- print((traceback.format_exc()))
- return 0
diff --git a/autosubmit_api/workers/business/process_graph_drawings.py b/autosubmit_api/workers/business/process_graph_drawings.py
index 7a5177244baffcda2ae24929bf6fc4a96a9c2690..c6fa84cf120f8cb2e02b1e9c0063041d22c7d472 100644
--- a/autosubmit_api/workers/business/process_graph_drawings.py
+++ b/autosubmit_api/workers/business/process_graph_drawings.py
@@ -1,49 +1,78 @@
import time
import traceback
-from ...experiment import common_db_requests as DbRequests
-from ...common import utils as common_utils
-from ...database.db_jobdata import ExperimentGraphDrawing
-from ...builders.configuration_facade_builder import ConfigurationFacadeDirector, AutosubmitConfigurationFacadeBuilder
-from ...builders.joblist_loader_builder import JobListLoaderBuilder, JobListLoaderDirector, JobListHelperBuilder
+from autosubmit_api.database import tables
+from autosubmit_api.database.common import create_as_times_db_engine
+from autosubmit_api.common import utils as common_utils
+from autosubmit_api.database.db_jobdata import ExperimentGraphDrawing
+from autosubmit_api.builders.configuration_facade_builder import (
+ ConfigurationFacadeDirector,
+ AutosubmitConfigurationFacadeBuilder,
+)
+from autosubmit_api.builders.joblist_loader_builder import (
+ JobListLoaderBuilder,
+ JobListLoaderDirector,
+)
from typing import List, Any
+
def process_active_graphs():
- """
- Process the list of active experiments to generate the positioning of their graphs
- """
- try:
- currently_running = DbRequests.get_currently_running_experiments()
-
- for expid in currently_running:
-
- try:
- autosubmit_configuration_facade = ConfigurationFacadeDirector(AutosubmitConfigurationFacadeBuilder(expid)).build_autosubmit_configuration_facade()
- if common_utils.is_version_historical_ready(autosubmit_configuration_facade.get_autosubmit_version()):
- # job_count = currently_running.get(expid, 0)
- _process_graph(expid, autosubmit_configuration_facade.chunk_size)
- except Exception as exp:
- print((traceback.format_exc()))
- print(("Error while processing: {}".format(expid)))
-
- except Exception as exp:
- print((traceback.format_exc()))
- print(("Error while processing graph drawing: {}".format(exp)))
+ """
+ Process the list of active experiments to generate the positioning of their graphs
+ """
+ try:
+ with create_as_times_db_engine().connect() as conn:
+ query_result = conn.execute(
+ tables.experiment_status_table.select().where()
+ ).all()
+
+ active_experiments: List[str] = [exp.name for exp in query_result]
+
+ for expid in active_experiments:
+ try:
+ autosubmit_configuration_facade = ConfigurationFacadeDirector(
+ AutosubmitConfigurationFacadeBuilder(expid)
+ ).build_autosubmit_configuration_facade()
+ if common_utils.is_version_historical_ready(
+ autosubmit_configuration_facade.get_autosubmit_version()
+ ):
+ _process_graph(expid, autosubmit_configuration_facade.chunk_size)
+ except Exception as exp:
+ print((traceback.format_exc()))
+ print(("Error while processing: {}".format(expid)))
+
+ except Exception as exp:
+ print((traceback.format_exc()))
+ print(("Error while processing graph drawing: {}".format(exp)))
+
def _process_graph(expid, chunk_size):
- # type: (str, int) -> List[Any] | None
- result = None
- experimentGraphDrawing = ExperimentGraphDrawing(expid)
- locked = experimentGraphDrawing.locked
- # print("Start Processing {} with {} jobs".format(expid, job_count))
- if not locked:
- start_time = time.time()
- job_list_loader = JobListLoaderDirector(JobListLoaderBuilder(expid)).build_loaded_joblist_loader()
- current_data = experimentGraphDrawing.get_validated_data(job_list_loader.jobs)
- if not current_data:
- print(("Must update {}".format(expid)))
- result = experimentGraphDrawing.calculate_drawing(job_list_loader.jobs, independent=False, num_chunks=chunk_size, job_dictionary=job_list_loader.job_dictionary)
- print(("Time Spent in {}: {} seconds.".format(expid, int(time.time() - start_time))))
- else:
- print(("{} Locked".format(expid)))
-
- return result
\ No newline at end of file
+ # type: (str, int) -> List[Any] | None
+ result = None
+ experimentGraphDrawing = ExperimentGraphDrawing(expid)
+ locked = experimentGraphDrawing.locked
+ # print("Start Processing {} with {} jobs".format(expid, job_count))
+ if not locked:
+ start_time = time.time()
+ job_list_loader = JobListLoaderDirector(
+ JobListLoaderBuilder(expid)
+ ).build_loaded_joblist_loader()
+ current_data = experimentGraphDrawing.get_validated_data(job_list_loader.jobs)
+ if not current_data:
+ print(("Must update {}".format(expid)))
+ result = experimentGraphDrawing.calculate_drawing(
+ job_list_loader.jobs,
+ independent=False,
+ num_chunks=chunk_size,
+ job_dictionary=job_list_loader.job_dictionary,
+ )
+ print(
+ (
+ "Time Spent in {}: {} seconds.".format(
+ expid, int(time.time() - start_time)
+ )
+ )
+ )
+ else:
+ print(("{} Locked".format(expid)))
+
+ return result
diff --git a/autosubmit_api/workers/populate_details/test.py b/autosubmit_api/workers/populate_details/test.py
deleted file mode 100644
index 24fc30a64f7aebc5da308e883ae608af00dbae6e..0000000000000000000000000000000000000000
--- a/autosubmit_api/workers/populate_details/test.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import unittest
-from autosubmit_api.workers.populate_details.populate import DetailsProcessor
-import autosubmit_api.common.utils_for_testing as TestUtils
-
-class TestPopulate(unittest.TestCase):
- def setUp(self):
- pass
-
- def test_retrieve_correct_experiments(self):
- # Arrange
- sut = DetailsProcessor(TestUtils.get_mock_basic_config())
- # Act
- experiments = sut._get_experiments()
- names = [experiment.name for experiment in experiments]
- # Assert
- self.assertIn("a28v", names)
- self.assertIn("a3tb", names)
-
-
- def test_get_details_from_experiment(self):
- # Arrange
- sut = DetailsProcessor(TestUtils.get_mock_basic_config())
- # Act
- details = sut._get_details_data_from_experiment("a28v")
- # Assert
- self.assertIsNotNone(details)
- self.assertEqual("wuruchi", details.owner)
- self.assertIsInstance(details.created, str)
- self.assertIsInstance(details.model, str)
- self.assertIsInstance(details.branch, str)
- self.assertEqual("marenostrum4", details.hpc)
-
-
- def test_get_all_experiment_details_equals_number_of_test_cases(self):
- # Arrange
- sut = DetailsProcessor(TestUtils.get_mock_basic_config())
- # Act
- processed_data = sut._get_all_details()
- # Assert
- self.assertEqual(len(processed_data), 2) # There are 2 cases in the test_cases folder
-
-
- def test_process_inserts_the_details(self):
- # Arrange
- sut = DetailsProcessor(TestUtils.get_mock_basic_config())
- # Act
- number_rows = sut.process()
- # Assert
- self.assertEqual(number_rows, 2)
-
-
-if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/workers/populate_queue_run_times.py b/autosubmit_api/workers/populate_queue_run_times.py
deleted file mode 100644
index 0236886edc3e53b184b234fc07c82d96c2cd4353..0000000000000000000000000000000000000000
--- a/autosubmit_api/workers/populate_queue_run_times.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from autosubmit_api.bgtasks.bgtask import PopulateQueueRuntimes
-
-
-def main():
- """ Process and updates queuing and running times. """
- PopulateQueueRuntimes.run()
-
-
-if __name__ == "__main__":
- main()
diff --git a/autosubmit_api/workers/populate_running_experiments.py b/autosubmit_api/workers/populate_running_experiments.py
index c0fe4cecc2487ede16e9c6567defd0dbd8e5e059..935553965b68a50a6e314d3fd412574eccbf42e0 100644
--- a/autosubmit_api/workers/populate_running_experiments.py
+++ b/autosubmit_api/workers/populate_running_experiments.py
@@ -1,11 +1,11 @@
# import autosubmitAPIwu.experiment.common_requests as ExperimentUtils
-from autosubmit_api.bgtasks.bgtask import PopulateRunningExperiments
+from autosubmit_api.bgtasks.tasks.status_updater import StatusUpdater
def main():
"""
Updates STATUS of experiments.
"""
- PopulateRunningExperiments.run()
+ StatusUpdater.run()
if __name__ == "__main__":
diff --git a/autosubmit_api/workers/test.py b/autosubmit_api/workers/test.py
deleted file mode 100644
index cd2a57baa7b046b0a2bd1b5eb1bb485abf3b757b..0000000000000000000000000000000000000000
--- a/autosubmit_api/workers/test.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import unittest
-
-import workers.business.process_graph_drawings as ProcessGraph
-from builders.configuration_facade_builder import ConfigurationFacadeDirector, AutosubmitConfigurationFacadeBuilder
-
-class TestGraphDraw(unittest.TestCase):
- def setUp(self):
- pass
-
- def test_graph_drawing_generator(self):
- EXPID = "a29z"
- autosubmit_configuration_facade = ConfigurationFacadeDirector(AutosubmitConfigurationFacadeBuilder(EXPID)).build_autosubmit_configuration_facade()
- result = ProcessGraph._process_graph(EXPID, autosubmit_configuration_facade.chunk_size)
- self.assertTrue(result == None)
-
-
-if __name__ == "__main__":
- unittest.main()
\ No newline at end of file
diff --git a/autosubmit_api/workers/verify_complete.py b/autosubmit_api/workers/verify_complete.py
deleted file mode 100644
index a8e85a4015ad811b73b53046f338641d76a8d236..0000000000000000000000000000000000000000
--- a/autosubmit_api/workers/verify_complete.py
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env python
-from autosubmit_api.bgtasks.bgtask import VerifyComplete
-
-
-def main():
- VerifyComplete.run()
-
-
-if __name__ == "__main__":
- main()
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000000000000000000000000000000000000..da75339225a307b2aa8beeb87be4a0a9755100fe
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,10 @@
+[pytest]
+addopts =
+ --cov=autosubmit_api --cov-config=.coveragerc --cov-report=html
+testpaths =
+ tests/
+doctest_optionflags =
+ NORMALIZE_WHITESPACE
+ IGNORE_EXCEPTION_DETAIL
+ ELLIPSIS
+
diff --git a/setup.py b/setup.py
index 8a648a87eeff97d984603f621376f2e2e12c824e..8eb87b2f76389b0717447cd8068c1bcba08d5453 100644
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,40 @@ def get_authors():
return autosubmit_api.__author__
+install_requires = [
+ "Flask~=2.2.5",
+ "pyjwt~=2.8.0",
+ "requests~=2.28.1",
+ "flask_cors~=3.0.10",
+ "bscearth.utils~=0.5.2",
+ "pysqlite-binary",
+ "pydotplus~=2.0.2",
+ "portalocker~=2.6.0",
+ "networkx~=2.6.3",
+ "scipy~=1.7.3",
+ "paramiko~=2.12.0",
+ "python-dotenv",
+ "autosubmitconfigparser~=1.0.48",
+ "autosubmit>=3.13",
+ "Flask-APScheduler",
+ "gunicorn",
+ "pydantic~=2.5.2",
+ "SQLAlchemy~=2.0.23",
+ "python-cas>=1.6.0",
+ "Authlib>=1.3.0"
+]
+
+# Test dependencies
+test_requires = [
+ "pytest",
+ "pytest-cov"
+]
+
+extras_require = {
+ 'test': test_requires,
+ 'all': install_requires + test_requires
+}
+
setup(
name="autosubmit_api",
version=get_version(),
@@ -32,28 +66,8 @@ setup(
packages=find_packages(),
keywords=["autosubmit", "API"],
python_requires=">=3.8",
- install_requires=[
- "Flask~=2.2.5",
- "pyjwt~=2.8.0",
- "requests~=2.28.1",
- "flask_cors~=3.0.10",
- "bscearth.utils~=0.5.2",
- "pysqlite-binary",
- "pydotplus~=2.0.2",
- "portalocker~=2.6.0",
- "networkx~=2.6.3",
- "scipy~=1.7.3",
- "paramiko~=2.12.0",
- "python-dotenv",
- "autosubmitconfigparser~=1.0.48",
- "autosubmit>=3.13",
- "Flask-APScheduler",
- "gunicorn",
- "pydantic~=2.5.2",
- "SQLAlchemy~=2.0.23",
- "python-cas>=1.6.0",
- "Authlib>=1.3.0"
- ],
+ install_requires=install_requires,
+ extras_require=extras_require,
include_package_data=True,
package_data={"autosubmit-api": ["README", "VERSION", "LICENSE"]},
classifiers=[
@@ -61,7 +75,7 @@ setup(
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: POSIX :: Linux",
- "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
],
entry_points={
"console_scripts": [
diff --git a/tests/bgtasks/test_status_updater.py b/tests/bgtasks/test_status_updater.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d2302956f97ffeb4eb5df736b4e3dfa6fbdece4
--- /dev/null
+++ b/tests/bgtasks/test_status_updater.py
@@ -0,0 +1,27 @@
+from autosubmit_api.bgtasks.tasks.status_updater import StatusUpdater
+from autosubmit_api.database import prepare_db, tables
+from autosubmit_api.database.common import (
+ create_autosubmit_db_engine,
+ create_as_times_db_engine,
+)
+from autosubmit_api.history.database_managers.database_models import RunningStatus
+
+class TestStatusUpdater:
+
+ def test_same_tables(self, fixture_mock_basic_config):
+ prepare_db()
+
+ StatusUpdater.run()
+
+ with create_autosubmit_db_engine().connect() as conn:
+ experiments = conn.execute(tables.experiment_table.select()).all()
+
+ with create_as_times_db_engine().connect() as conn:
+ exps_status = conn.execute(tables.experiment_status_table.select()).all()
+
+ assert len(experiments) == len(exps_status)
+ assert set([x.id for x in experiments]) == set([x.exp_id for x in exps_status])
+ assert set([x.name for x in experiments]) == set([x.name for x in exps_status])
+
+ for e_st in exps_status:
+ assert e_st.status in [RunningStatus.RUNNING, RunningStatus.NOT_RUNNING]
diff --git a/tests/conftest.py b/tests/conftest.py
index 8fa8be4f8f166fb246b74506667a36b30552843f..be699e00c6651b16daf191f6618656cbe26787b3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,4 @@
-# Conftest file for sharing fixtures
+# Conftest file for sharing fixtures
# Reference: https://docs.pytest.org/en/latest/reference/fixtures.html#conftest-py-sharing-fixtures-across-multiple-files
import os
@@ -7,6 +7,7 @@ import pytest
from autosubmitconfigparser.config.basicconfig import BasicConfig
from autosubmit_api.app import create_app
from autosubmit_api.config.basicConfig import APIBasicConfig
+from autosubmit_api import config
from tests.custom_utils import custom_return_value
FAKE_EXP_DIR = "./tests/experiments/"
@@ -15,20 +16,14 @@ FAKE_EXP_DIR = "./tests/experiments/"
#### FIXTURES ####
@pytest.fixture(autouse=True)
def fixture_disable_protection(monkeypatch: pytest.MonkeyPatch):
+ monkeypatch.setattr(config, "PROTECTION_LEVEL", "NONE")
monkeypatch.setenv("PROTECTION_LEVEL", "NONE")
+
@pytest.fixture
def fixture_mock_basic_config(monkeypatch: pytest.MonkeyPatch):
- # Patch APIBasicConfig parent BasicConfig
- monkeypatch.setattr(BasicConfig, "read", custom_return_value(None))
- monkeypatch.setattr(APIBasicConfig, "read", custom_return_value(None))
- monkeypatch.setattr(BasicConfig, "LOCAL_ROOT_DIR", FAKE_EXP_DIR)
- monkeypatch.setattr(BasicConfig, "DB_DIR", FAKE_EXP_DIR)
- monkeypatch.setattr(BasicConfig, "DB_FILE", "autosubmit.db")
- monkeypatch.setattr(
- BasicConfig, "DB_PATH", os.path.join(FAKE_EXP_DIR, "autosubmit.db")
- )
- monkeypatch.setattr(BasicConfig, "AS_TIMES_DB", "as_times.db")
+ # Get APIBasicConfig from file
+ monkeypatch.setenv("AUTOSUBMIT_CONFIGURATION", os.path.join(FAKE_EXP_DIR, ".autosubmitrc"))
yield APIBasicConfig
diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9e2ed0c111ddb1a0a3e94324f2c2e1ca628c987b
--- /dev/null
+++ b/tests/docker/Dockerfile
@@ -0,0 +1,17 @@
+FROM python:3.8-slim-bullseye
+
+WORKDIR /
+
+RUN apt-get update && apt-get install -y git graphviz
+
+RUN git clone https://earth.bsc.es/gitlab/es/autosubmit_api.git
+
+WORKDIR /autosubmit_api/
+
+RUN git checkout v4.0.0b4
+
+RUN pip install .
+
+RUN pip install -U pytest pytest-cov
+
+RUN pytest --cov=autosubmit_api --cov-config=.coveragerc --cov-report=html tests/
\ No newline at end of file
diff --git a/tests/experiments/.autosubmitrc b/tests/experiments/.autosubmitrc
new file mode 100644
index 0000000000000000000000000000000000000000..da7bbe7c1b55fa565084366bcb41f5026c2cb561
--- /dev/null
+++ b/tests/experiments/.autosubmitrc
@@ -0,0 +1,21 @@
+[database]
+path = ./tests/experiments/
+filename = autosubmit.db
+
+[local]
+path = ./tests/experiments/
+
+[globallogs]
+path = ./tests/experiments/logs
+
+[historicdb]
+path = ./tests/experiments/metadata/data
+
+[structures]
+path = ./tests/experiments/metadata/structures
+
+[historiclog]
+path = ./tests/experiments/metadata/logs
+
+[graph]
+path = ./tests/experiments/metadata/graph
\ No newline at end of file
diff --git a/tests/experiments/as_times.db b/tests/experiments/as_times.db
index ef93733acc0d1b1e25f8f3577df6df68f498c66a..0f8be2ec756ee0c158c810bbb21ecc5baa5089d0 100644
Binary files a/tests/experiments/as_times.db and b/tests/experiments/as_times.db differ
diff --git a/tests/experiments/metadata/data/job_data_a007.db b/tests/experiments/metadata/data/job_data_a007.db
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/experiments/metadata/data/job_data_a3tb.db b/tests/experiments/metadata/data/job_data_a3tb.db
new file mode 100644
index 0000000000000000000000000000000000000000..b5ead66dab09d063c5023a17bd17300211ea848f
Binary files /dev/null and b/tests/experiments/metadata/data/job_data_a3tb.db differ
diff --git a/tests/experiments/metadata/graph/graph_data_a003.db b/tests/experiments/metadata/graph/graph_data_a003.db
new file mode 100755
index 0000000000000000000000000000000000000000..ed0362b552850f04244080a7fe51840c7fdaf54c
Binary files /dev/null and b/tests/experiments/metadata/graph/graph_data_a003.db differ
diff --git a/tests/experiments/as_metadata/graph/graph_data_a003.db b/tests/experiments/metadata/graph/graph_data_a3tb.db
similarity index 100%
rename from tests/experiments/as_metadata/graph/graph_data_a003.db
rename to tests/experiments/metadata/graph/graph_data_a3tb.db
diff --git a/tests/experiments/metadata/structures/structure_a003.db b/tests/experiments/metadata/structures/structure_a003.db
new file mode 100644
index 0000000000000000000000000000000000000000..47b5ed9737213db2ab78234bdded4a6d587da00f
Binary files /dev/null and b/tests/experiments/metadata/structures/structure_a003.db differ
diff --git a/tests/experiments/metadata/structures/structure_a007.db b/tests/experiments/metadata/structures/structure_a007.db
new file mode 100644
index 0000000000000000000000000000000000000000..1db581bacb13626eed16874a91a27b31f468662b
Binary files /dev/null and b/tests/experiments/metadata/structures/structure_a007.db differ
diff --git a/tests/experiments/metadata/structures/structure_a3tb.db b/tests/experiments/metadata/structures/structure_a3tb.db
new file mode 100755
index 0000000000000000000000000000000000000000..45dbb0f57b16cd6af2db84180d89348307c2729c
Binary files /dev/null and b/tests/experiments/metadata/structures/structure_a3tb.db differ
diff --git a/tests/test_auth.py b/tests/test_auth.py
index ce59c9da09f466e619b7f82698882932699e7685..5fef2066abbd3e6810b817ed6a9bf6ee296f49a3 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -1,13 +1,19 @@
+import os
from uuid import uuid4
import pytest
from autosubmit_api.auth import ProtectionLevels, with_auth_token
from autosubmit_api import auth
from autosubmit_api.auth.utils import validate_client
from autosubmit_api.config.basicConfig import APIBasicConfig
+from autosubmit_api import config
from tests.custom_utils import custom_return_value, dummy_response
class TestCommonAuth:
+ def test_mock_env_protection_level(self):
+ assert os.environ.get("PROTECTION_LEVEL") == "NONE"
+ assert config.PROTECTION_LEVEL == "NONE"
+
def test_levels_enum(self):
assert ProtectionLevels.ALL > ProtectionLevels.WRITEONLY
assert ProtectionLevels.WRITEONLY > ProtectionLevels.NONE
diff --git a/tests/test_config.py b/tests/test_config.py
index e2e08d7e6ced5ea1d80249679d4dbf00c83f5d81..64b12455f13e1ce7742ece32dff1b4cbfeb3023c 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -6,10 +6,39 @@ from autosubmit_api.config.basicConfig import APIBasicConfig
from autosubmit_api.config.config_common import AutosubmitConfigResolver
from autosubmit_api.config.ymlConfigStrategy import ymlConfigStrategy
+from tests.conftest import FAKE_EXP_DIR
from tests.custom_utils import custom_return_value
+class TestBasicConfig:
+ def test_api_basic_config(self, fixture_mock_basic_config):
+ APIBasicConfig.read()
+
+ assert os.getenv("AUTOSUBMIT_CONFIGURATION") == os.path.join(
+ FAKE_EXP_DIR, ".autosubmitrc"
+ )
+ assert APIBasicConfig.LOCAL_ROOT_DIR == FAKE_EXP_DIR
+ assert APIBasicConfig.DB_FILE == "autosubmit.db"
+ assert APIBasicConfig.DB_PATH == os.path.join(
+ FAKE_EXP_DIR, APIBasicConfig.DB_FILE
+ )
+ assert APIBasicConfig.AS_TIMES_DB == "as_times.db"
+ assert APIBasicConfig.JOBDATA_DIR == os.path.join(
+ FAKE_EXP_DIR, "metadata", "data"
+ )
+ assert APIBasicConfig.GLOBAL_LOG_DIR == os.path.join(FAKE_EXP_DIR, "logs")
+ assert APIBasicConfig.STRUCTURES_DIR == os.path.join(
+ FAKE_EXP_DIR, "metadata", "structures"
+ )
+ assert APIBasicConfig.HISTORICAL_LOG_DIR == os.path.join(
+ FAKE_EXP_DIR, "metadata", "logs"
+ )
+
+ assert APIBasicConfig.GRAPHDATA_DIR == os.path.join(
+ FAKE_EXP_DIR, "metadata", "graph"
+ )
+
class TestConfigResolver:
def test_simple_init(self, monkeypatch: pytest.MonkeyPatch):
# Conf test decision
diff --git a/tests/test_endpoints_v3.py b/tests/test_endpoints_v3.py
index d410ec94c326d82d226102ba3c5e18b692816fe1..4d5b036720c8f69b3c91a60264214d8d1a8cf698 100644
--- a/tests/test_endpoints_v3.py
+++ b/tests/test_endpoints_v3.py
@@ -89,6 +89,7 @@ class TestExpInfo:
expid = "a003"
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["expid"] == expid
assert resp_obj["total_jobs"] == 8
@@ -97,10 +98,11 @@ class TestExpInfo:
expid = "a3tb"
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["expid"] == expid
assert resp_obj["total_jobs"] == 55
- assert resp_obj["completed_jobs"] == 24
+ assert resp_obj["completed_jobs"] == 28
class TestPerformance:
@@ -113,12 +115,14 @@ class TestPerformance:
expid = "a007"
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["Parallelization"] == 8
expid = "a3tb"
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["Parallelization"] == 768
@@ -129,6 +133,7 @@ class TestPerformance:
expid = "a003"
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["Parallelization"] == 16
@@ -145,6 +150,7 @@ class TestTree:
)
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total"] == 8
assert resp_obj["total"] == len(resp_obj["jobs"])
@@ -160,6 +166,7 @@ class TestTree:
)
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total"] == 55
assert resp_obj["total"] == len(resp_obj["jobs"])
@@ -179,6 +186,7 @@ class TestRunsList:
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert isinstance(resp_obj["runs"], list)
@@ -192,6 +200,7 @@ class TestRunDetail:
response = fixture_client.get(self.endpoint.format(expid=expid, runId=2))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total"] == 8
@@ -204,6 +213,7 @@ class TestQuick:
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total"] == len(resp_obj["tree_view"])
assert resp_obj["total"] == len(resp_obj["view_data"])
@@ -220,7 +230,7 @@ class TestGraph:
query_string={"loggedUser": random_user},
)
resp_obj: dict = response.get_json()
-
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
@@ -234,7 +244,7 @@ class TestGraph:
query_string={"loggedUser": random_user},
)
resp_obj: dict = response.get_json()
-
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
@@ -246,7 +256,7 @@ class TestGraph:
query_string={"loggedUser": random_user},
)
resp_obj: dict = response.get_json()
-
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
@@ -258,7 +268,45 @@ class TestGraph:
query_string={"loggedUser": random_user},
)
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
+ assert resp_obj["error"] == False
+ assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
+
+ def test_graph_standard_none_retro3(self, fixture_client: FlaskClient):
+ expid = "a3tb"
+ random_user = str(uuid4())
+ response = fixture_client.get(
+ self.endpoint.format(expid=expid, graph_type="standard", grouped="none"),
+ query_string={"loggedUser": random_user},
+ )
+ resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
+ assert resp_obj["error"] == False
+ assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
+
+ def test_graph_standard_datemember_retro3(self, fixture_client: FlaskClient):
+ expid = "a3tb"
+ random_user = str(uuid4())
+ response = fixture_client.get(
+ self.endpoint.format(
+ expid=expid, graph_type="standard", grouped="date-member"
+ ),
+ query_string={"loggedUser": random_user},
+ )
+ resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
+ assert resp_obj["error"] == False
+ assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
+ def test_graph_standard_status_retro3(self, fixture_client: FlaskClient):
+ expid = "a3tb"
+ random_user = str(uuid4())
+ response = fixture_client.get(
+ self.endpoint.format(expid=expid, graph_type="standard", grouped="status"),
+ query_string={"loggedUser": random_user},
+ )
+ resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total_jobs"] == len(resp_obj["nodes"])
@@ -271,6 +319,7 @@ class TestExpCount:
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total"] == sum(
[resp_obj["counters"][key] for key in resp_obj["counters"]]
@@ -284,6 +333,7 @@ class TestExpCount:
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["total"] == sum(
[resp_obj["counters"][key] for key in resp_obj["counters"]]
@@ -308,6 +358,7 @@ class TestSummary:
)
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["n_sim"] > 0
@@ -322,6 +373,7 @@ class TestStatistics:
)
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["Statistics"]["Period"]["From"] == "None"
@@ -334,21 +386,19 @@ class TestCurrentConfig:
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert (
resp_obj["configuration_filesystem"]["CONFIG"]["AUTOSUBMIT_VERSION"]
== "4.0.95"
)
- assert (
- resp_obj["configuration_current_run"]["CONFIG"]["AUTOSUBMIT_VERSION"]
- == "4.0.101"
- )
def test_retrocomp_v3_conf_files(self, fixture_client: FlaskClient):
expid = "a3tb"
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert (
resp_obj["configuration_filesystem"]["conf"]["config"]["AUTOSUBMIT_VERSION"]
@@ -364,6 +414,7 @@ class TestPklInfo:
response = fixture_client.get(self.endpoint.format(expid=expid, timestamp=0))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert len(resp_obj["pkl_content"]) == 8
@@ -379,6 +430,7 @@ class TestPklTreeInfo:
response = fixture_client.get(self.endpoint.format(expid=expid, timestamp=0))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert len(resp_obj["pkl_content"]) == 8
@@ -394,5 +446,6 @@ class TestExpRunLog:
response = fixture_client.get(self.endpoint.format(expid=expid))
resp_obj: dict = response.get_json()
+ assert resp_obj["error_message"] == ""
assert resp_obj["error"] == False
assert resp_obj["found"] == True
diff --git a/tests/test_graph.py b/tests/test_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..0dc5beda06a9f91b2944b9fd402bf0ce305f8f7a
--- /dev/null
+++ b/tests/test_graph.py
@@ -0,0 +1,75 @@
+import os
+
+from sqlalchemy import create_engine
+from autosubmit_api.builders.configuration_facade_builder import (
+ AutosubmitConfigurationFacadeBuilder,
+ ConfigurationFacadeDirector,
+)
+from autosubmit_api.builders.joblist_loader_builder import (
+ JobListLoaderBuilder,
+ JobListLoaderDirector,
+)
+from autosubmit_api.database import tables
+from autosubmit_api.database.db_jobdata import ExperimentGraphDrawing
+from autosubmit_api.monitor.monitor import Monitor
+from autosubmit_api.persistance.experiment import ExperimentPaths
+
+
+class TestPopulateDB:
+
+ def test_monitor_dot(self, fixture_mock_basic_config):
+ expid = "a003"
+ job_list_loader = JobListLoaderDirector(
+ JobListLoaderBuilder(expid)
+ ).build_loaded_joblist_loader()
+
+ monitor = Monitor()
+ graph = monitor.create_tree_list(
+ expid,
+ job_list_loader.jobs,
+ None,
+ dict(),
+ False,
+ job_list_loader.job_dictionary,
+ )
+ assert graph
+
+ result = graph.create("dot", format="plain")
+ assert result and len(result) > 0
+
+ def test_process_graph(self, fixture_mock_basic_config):
+ expid = "a003"
+ experimentGraphDrawing = ExperimentGraphDrawing(expid)
+ job_list_loader = JobListLoaderDirector(
+ JobListLoaderBuilder(expid)
+ ).build_loaded_joblist_loader()
+
+ autosubmit_configuration_facade = ConfigurationFacadeDirector(
+ AutosubmitConfigurationFacadeBuilder(expid)
+ ).build_autosubmit_configuration_facade()
+
+ exp_paths = ExperimentPaths(expid)
+ with create_engine(
+ f"sqlite:///{ os.path.abspath(exp_paths.graph_data_db)}"
+ ).connect() as conn:
+ conn.execute(tables.graph_data_table.delete())
+ conn.commit()
+
+ experimentGraphDrawing.calculate_drawing(
+ allJobs=job_list_loader.jobs,
+ independent=False,
+ num_chunks=autosubmit_configuration_facade.chunk_size,
+ job_dictionary=job_list_loader.job_dictionary,
+ )
+
+ assert (
+ experimentGraphDrawing.coordinates
+ and len(experimentGraphDrawing.coordinates) == 8
+ )
+
+ rows = conn.execute(tables.graph_data_table.select()).all()
+
+ assert len(rows) == 8
+ for job in rows:
+ job_name: str = job.job_name
+ assert job_name.startswith(expid)