From f679c0513643219f8178eb9a3a796f6a510805b1 Mon Sep 17 00:00:00 2001 From: Evan Carlin Date: Wed, 28 Aug 2024 22:50:29 +0000 Subject: [PATCH] Fix #7231: Raydata fixes for scan detailed status --- .../static/html/raydata-run-analysis.html | 2 +- sirepo/package_data/static/js/raydata.js | 128 ++++++++---------- sirepo/raydata/analysis_driver/__init__.py | 17 ++- sirepo/raydata/analysis_driver/chx.py | 28 +++- 4 files changed, 93 insertions(+), 82 deletions(-) diff --git a/sirepo/package_data/static/html/raydata-run-analysis.html b/sirepo/package_data/static/html/raydata-run-analysis.html index 2277526d45..a105fdb167 100644 --- a/sirepo/package_data/static/html/raydata-run-analysis.html +++ b/sirepo/package_data/static/html/raydata-run-analysis.html @@ -5,7 +5,7 @@
-
+
diff --git a/sirepo/package_data/static/js/raydata.js b/sirepo/package_data/static/js/raydata.js index 1783445e65..d7065250bf 100644 --- a/sirepo/package_data/static/js/raydata.js +++ b/sirepo/package_data/static/js/raydata.js @@ -1198,83 +1198,73 @@ SIREPO.app.directive('scanDetail', function() { template: `
Scan Detail
-
-
Scan Id: {{ scan.rduid }}
-
Analysis Elapsed Time: {{ analysisElapsedTime() }} seconds
-
-
Current Consecutive Failures: {{ consecutiveFailures() }}
-
-
-
Most Recent Status
-
{{ currentStatus() }}
-
-
-
Detailed Status File
-
{{ detailedStatus() }}
+
+
Scan Id: {{ scan.rduid }}
+
Analysis Elapsed Time: {{ analysisElapsedTime() }} seconds
+
+
Current Status: {{ scan.status }}
+
+ Detailed Status: +
    +
  • + {{ stepName }} +
      +
    • + {{ key }}: {{ parseTime(stepInfo[key]) }} +
    • +
    • elapsed: {{ stepElapsed(stepInfo) }}
    • +
    • status: {{ stepStatus(stepInfo) }}
    • +
    +
  • +
+
+
-
-`, - controller: function($scope, columnsService, utilities) { - function failureInRun(run) { - let r = false; - for (const f of Object.values($scope.detailedStatusFile()[run])) { - if (f.status === 'failed') { - r = true; - } - } - return r; - } - - function getSortedRunIndexes() { - return Object.keys($scope.detailedStatusFile()).map((x) => parseInt(x)).sort(); - } + `, + controller: function($filter, $scope, columnsService, raydataService, utilities) { + $scope.latestDetailedStatus = null; - function mostRecentAnalysisDetails() { - return $scope.detailedStatusFile()? $scope.detailedStatusFile()[Math.max(...getSortedRunIndexes())] : ''; - } + function setLatestDetailedStatus() { + $scope.latestDetailedStatus = $scope.scan.detailed_status[Math.max(Object.keys($scope.scan.detailed_status))]; + } $scope.analysisElapsedTime = () => { return $scope.scan && $scope.scan.analysis_elapsed_time ? $scope.scan.analysis_elapsed_time : null; }; - $scope.consecutiveFailures = () => { - if (! $scope.detailedStatusFile()) { - return ''; - } - let r = 0; - for (const k of getSortedRunIndexes().reverse()) { - if (failureInRun(k)) { - r += 1; - } else { - return r; - } - } - - return r; - }; - - $scope.currentStatus = () => { - let r = ''; - for (const k of Object.keys(mostRecentAnalysisDetails())) { - r += k + ': ' + mostRecentAnalysisDetails()[k].status + '\n'; - } - return r; - }; - - $scope.detailedStatus = () => { - return utilities.objectToText($scope.detailedStatusFile()).replace( - /(start:|stop:)(\s*)(\d+\.?\d*)/gi, - (_, p1, p2, p3) => { - return p1 + p2 + (new Date(parseFloat(p3)*1000)).toString(); - } - ) - ; - }; - - $scope.detailedStatusFile = () => { - return $scope.scan && $scope.scan.detailed_status && Object.keys($scope.scan.detailed_status).length > 0 ? $scope.scan.detailed_status : null; - }; + $scope.isEmptyObject = (obj) => { + return $.isEmptyObject(obj) + } + + $scope.parseTime = (unixTime) => { + return (new Date(unixTime * 1000)).toString() + }; + + $scope.stepElapsed = (stepInfo) => { + if (stepInfo['start'] && stepInfo['stop']) { + return $filter('date')((stepInfo['stop'] - stepInfo['start']) * 1000, 'HH:mm:ss', 'UTC'); + } + return null; + }; + + $scope.stepStatus = (stepInfo) => { + function pendingIfRunningElseError(scanStatus) { + if (raydataService.ANALYSIS_STATUS_NON_STOPPED.includes(scanStatus)) { + return 'pending'; + } + return 'error'; + } + + // start and stop have been set so the notebook was + // able to manage setting the status itself. + if (stepInfo['start'] && stepInfo['stop']) { + return stepInfo['status']; + } + return pendingIfRunningElseError($scope.scan.status); + }; + + $scope.$watch('scan', setLatestDetailedStatus); }, }; }); diff --git a/sirepo/raydata/analysis_driver/__init__.py b/sirepo/raydata/analysis_driver/__init__.py index 828b1b5992..701469ef4e 100644 --- a/sirepo/raydata/analysis_driver/__init__.py +++ b/sirepo/raydata/analysis_driver/__init__.py @@ -39,6 +39,11 @@ def get_notebooks(self, *args, **kwargs): raise NotImplementedError("children must implement this method") def get_output(self): + def load_json(path): + d = pkjson.load_any(path) + # CHX json are double encoded so may need to load 2x + return pkjson.load_any(d) if isinstance(d, str) else d + res = PKDict() for e in [ PKDict( @@ -53,7 +58,7 @@ def get_output(self): PKDict( name="jsonFiles", file_type="json", - op=pkjson.load_any, + op=load_json, ), ]: res[e.name] = [ @@ -69,12 +74,14 @@ def get_output_dir(self): def get_papermill_args(self): res = [] - for n, v in [ - ["uid", self.rduid], - ["scan", self.rduid], + for a in [ + PKDict(name="uid", value=self.rduid), + PKDict(name="scan", value=self.rduid), *self._get_papermill_args(), ]: - res.extend(["-p", f"'{n}'", f"'{v}'"]) + res.extend( + ["-r" if a.get("raw_param") else "-p", f"'{a.name}'", f"'{a.value}'"] + ) res.extend(("--report-mode", "--log-output", "--progress-bar")) return res diff --git a/sirepo/raydata/analysis_driver/chx.py b/sirepo/raydata/analysis_driver/chx.py index ba5ef36dad..3175a42c70 100644 --- a/sirepo/raydata/analysis_driver/chx.py +++ b/sirepo/raydata/analysis_driver/chx.py @@ -21,10 +21,14 @@ def get_conda_env(self): def get_detailed_status_file(self, rduid): p = self.get_output_dir().join(f"progress_dict_{rduid}.json") - if os.path.exists(p): - with open(p, "r") as f: - return pkjson.load_any(f) - return PKDict() + if not p.check(): + return PKDict() + d = pkjson.load_any(p) + # The notebooks do json.dump(json.dumps(progress_dict), outfile) + # which double encodes the json object. So, we may + # need to decode it 2x. Be compliant either way in case this + # changes in the future. + return pkjson.load_any(d) if isinstance(d, str) else d def get_notebooks(self): return [ @@ -54,9 +58,19 @@ def get_output_dir(self): def _get_papermill_args(self, *args, **kwargs): return [ - ["run_two_time", True], - ["run_dose", False], - ["username", self._scan_metadata.get_start_field("user")], + # Cycle can look like 2024_2 which is converted to int by papermill unless raw_param=True + PKDict( + name="cycle", + value=self._scan_metadata.get_start_field("cycle"), + raw_param=True, + ), + # POSIT: Same as AutoRun_functions.get_process_id + PKDict(name="process_id", value=f"{self.rduid}_0"), + PKDict(name="username", value=self._scan_metadata.get_start_field("user")), + PKDict( + name="user_group", + value=self._scan_metadata.get_start_field("user_group", unchecked=True), + ), ]