Coverage for python/lsst/ctrl/mpexec/reports.py: 80%
62 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-24 01:54 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-24 01:54 -0700
1# This file is part of ctrl_mpexec.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = ["ExecutionStatus", "Report", "QuantumReport"]
26import enum
27import sys
28from typing import Dict, List, Optional
30from lsst.daf.butler import DataCoordinate, DataId, DataIdValue
31from lsst.utils.introspection import get_full_type_name
32from pydantic import BaseModel, validator
35def _serializeDataId(dataId: DataId) -> Dict[str, DataIdValue]:
36 if isinstance(dataId, DataCoordinate):
37 return dataId.byName()
38 else:
39 return dataId # type: ignore
42class ExecutionStatus(enum.Enum):
43 """Possible values for job execution status.
45 Status `FAILURE` is set if one or more tasks failed. Status `TIMEOUT` is
46 set if there are no failures but one or more tasks timed out. Timeouts can
47 only be detected in multi-process mode, child task is killed on timeout
48 and usually should have non-zero exit code.
49 """
51 SUCCESS = "success"
52 FAILURE = "failure"
53 TIMEOUT = "timeout"
54 SKIPPED = "skipped"
57class ExceptionInfo(BaseModel):
58 """Information about exception."""
60 className: str
61 """Name of the exception class if exception was raised."""
63 message: str
64 """Exception message for in-process quantum execution, None if
65 quantum was executed in sub-process.
66 """
68 @classmethod
69 def from_exception(cls, exception: Exception) -> ExceptionInfo:
70 """Construct instance from an exception."""
71 return cls(className=get_full_type_name(exception), message=str(exception))
74class QuantumReport(BaseModel):
75 """Task execution report for a single Quantum."""
77 status: ExecutionStatus = ExecutionStatus.SUCCESS
78 """Execution status, one of the values in `ExecutionStatus` enum."""
80 dataId: Dict[str, DataIdValue]
81 """Quantum DataId."""
83 taskLabel: Optional[str]
84 """Label for a task executing this Quantum."""
86 exitCode: Optional[int] = None
87 """Exit code for a sub-process executing Quantum, None for in-process
88 Quantum execution. Negative if process was killed by a signal.
89 """
91 exceptionInfo: Optional[ExceptionInfo] = None
92 """Exception information if exception was raised."""
94 def __init__(
95 self,
96 dataId: DataId,
97 taskLabel: str,
98 status: ExecutionStatus = ExecutionStatus.SUCCESS,
99 exitCode: Optional[int] = None,
100 exceptionInfo: Optional[ExceptionInfo] = None,
101 ):
102 super().__init__(
103 status=status,
104 dataId=_serializeDataId(dataId),
105 taskLabel=taskLabel,
106 exitCode=exitCode,
107 exceptionInfo=exceptionInfo,
108 )
110 @classmethod
111 def from_exception(
112 cls,
113 exception: Exception,
114 dataId: DataId,
115 taskLabel: str,
116 ) -> QuantumReport:
117 """Construct report instance from an exception and other pieces of
118 data.
119 """
120 return cls(
121 status=ExecutionStatus.FAILURE,
122 dataId=dataId,
123 taskLabel=taskLabel,
124 exceptionInfo=ExceptionInfo.from_exception(exception),
125 )
127 @classmethod
128 def from_exit_code(
129 cls,
130 exitCode: int,
131 dataId: DataId,
132 taskLabel: str,
133 ) -> QuantumReport:
134 """Construct report instance from an exit code and other pieces of
135 data.
136 """
137 return cls(
138 status=ExecutionStatus.SUCCESS if exitCode == 0 else ExecutionStatus.FAILURE,
139 dataId=dataId,
140 taskLabel=taskLabel,
141 exitCode=exitCode,
142 )
145class Report(BaseModel):
146 """Execution report for the whole job with one or few quanta."""
148 status: ExecutionStatus = ExecutionStatus.SUCCESS
149 """Job status."""
151 cmdLine: Optional[List[str]] = None
152 """Command line for the whole job."""
154 exitCode: Optional[int] = None
155 """Job exit code, this obviously cannot be set in pipetask."""
157 exceptionInfo: Optional[ExceptionInfo] = None
158 """Exception information if exception was raised."""
160 quantaReports: List[QuantumReport] = []
161 """List of per-quantum reports, ordering is not specified. Some or all
162 quanta may not produce a report.
163 """
165 @validator("cmdLine", always=True)
166 def _set_cmdLine(cls, v: Optional[List[str]]) -> List[str]: # noqa: N805
167 if v is None:
168 v = sys.argv
169 return v
171 def set_exception(self, exception: Exception) -> None:
172 """Update exception information from an exception object."""
173 self.exceptionInfo = ExceptionInfo.from_exception(exception)