Пример #1
0
 def __init__(self, parent=None) -> None:
     super().__init__(parent)
     self.root = Node(None, {}, NodeType.ROOT)
Пример #2
0
class SnapshotModel(QAbstractItemModel):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.root = Node(None, {}, NodeType.ROOT)

    def _add_partial_snapshot(self, partial: PartialSnapshot, iter_: int):
        partial_dict = partial.to_dict()
        partial_s = SnapshotDict(**partial_dict)
        if iter_ not in self.root.children:
            logger.debug("no full snapshot yet, bailing")
            return
        iter_index = self.index(iter_, 0, QModelIndex())
        iter_node = self.root.children[iter_]
        if not partial_s.reals:
            logger.debug(f"no realizations in partial for iter {iter_}")
            return
        for real_id in sorted(partial_s.reals, key=int):
            real = partial_s.reals[real_id]
            real_node = iter_node.children[real_id]
            if real.status:
                real_node.data[ids.STATUS] = real.status

            real_index = self.index(real_node.row(), 0, iter_index)
            real_index_bottom_right = self.index(
                real_node.row(),
                self.columnCount(iter_index) - 1, iter_index)

            if not real.steps:
                continue

            for step_id, step in real.steps.items():
                step_node = real_node.children[step_id]
                if step.status:
                    step_node.data[ids.STATUS] = step.status

                step_index = self.index(step_node.row(), 0, real_index)
                step_index_bottom_right = self.index(
                    step_node.row(),
                    self.columnCount(real_index) - 1, real_index)

                if not step.jobs:
                    continue

                for job_id in sorted(step.jobs, key=int):
                    job = step.jobs[job_id]
                    job_node = step_node.children[job_id]

                    if job.status:
                        job_node.data[ids.STATUS] = job.status
                    if job.start_time:
                        job_node.data[ids.START_TIME] = job.start_time
                    if job.end_time:
                        job_node.data[ids.END_TIME] = job.end_time
                    if job.stdout:
                        job_node.data[ids.STDOUT] = job.stdout
                    if job.stderr:
                        job_node.data[ids.STDERR] = job.stderr

                    # Errors may be unset as the queue restarts the job
                    job_node.data[ids.ERROR] = job.error if job.error else ""

                    for attr in (ids.CURRENT_MEMORY_USAGE,
                                 ids.MAX_MEMORY_USAGE):
                        if job.data and attr in job.data:
                            job_node.data[ids.DATA][attr] = job.data.get(attr)

                    job_index = self.index(job_node.row(), 0, step_index)
                    job_index_bottom_right = self.index(
                        job_node.row(),
                        self.columnCount() - 1, step_index)
                    self.dataChanged.emit(job_index, job_index_bottom_right)
                self.dataChanged.emit(step_index, step_index_bottom_right)
            self.dataChanged.emit(real_index, real_index_bottom_right)
            # TODO: there is no check that any of the data *actually* changed
            # https://github.com/equinor/ert/issues/1374

        top_left = self.index(0, 0, iter_index)
        bottom_right = self.index(0, 1, iter_index)
        self.dataChanged.emit(top_left, bottom_right)

    def _add_snapshot(self, snapshot: Snapshot, iter_: int):
        snapshot_tree = snapshot_to_tree(snapshot, iter_)
        if iter_ in self.root.children:
            self.modelAboutToBeReset.emit()
            self.root.children[iter_] = snapshot_tree
            snapshot_tree.parent = self.root
            self.modelReset.emit()
            return

        parent = QModelIndex()
        next_iter = len(self.root.children)
        self.beginInsertRows(parent, next_iter, next_iter)
        self.root.add_child(snapshot_tree)
        self.root.children[iter_] = snapshot_tree
        self.rowsInserted.emit(parent, snapshot_tree.row(),
                               snapshot_tree.row())

    def columnCount(self, parent=QModelIndex()):
        parent_node = parent.internalPointer()
        if parent_node is None:
            return len(COLUMNS[NodeType.ROOT])
        return len(COLUMNS[parent_node.type])

    def rowCount(self, parent=QModelIndex()):
        if not parent.isValid():
            parentItem = self.root
        else:
            parentItem = parent.internalPointer()

        if parent.column() > 0:
            return 0

        return len(parentItem.children)

    def parent(self, index: QModelIndex):
        if not index.isValid():
            return QModelIndex()

        child_item = index.internalPointer()
        if not hasattr(child_item, "parent"):
            raise ValueError(
                f"index r{index.row()}/c{index.column()} pointed to parent-less item {child_item}"
            )
        parentItem = child_item.parent

        if parentItem == self.root:
            return QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    def data(self, index: QModelIndex, role=Qt.DisplayRole):
        if not index.isValid():
            return QVariant()

        if role == Qt.TextAlignmentRole:
            return Qt.AlignCenter

        node = index.internalPointer()

        if role == NodeRole:
            return node

        if node.type == NodeType.JOB:
            return self._job_data(index, node, role)
        elif node.type == NodeType.REAL:
            return self._real_data(index, node, role)

        if role == Qt.DisplayRole:
            if index.column() == 0:
                return f"{node.type}:{node.id}"
            if index.column() == 1:
                return f"{node.data['status']}"

        if role in (Qt.StatusTipRole, Qt.WhatsThisRole, Qt.ToolTipRole):
            return ""

        if role == Qt.SizeHintRole:
            return QSize()

        if role == Qt.FontRole:
            return QFont()

        if role in (Qt.BackgroundRole, Qt.ForegroundRole, Qt.DecorationRole):
            return QColor()

        return QVariant()

    def _real_data(self, index: QModelIndex, node: Node, role: int):
        if role == RealJobColorHint:
            colors = []
            assert node.type == NodeType.REAL
            for step in node.children.values():
                for job_id in sorted(step.children.keys(), key=int):
                    status = step.children[job_id].data[ids.STATUS]
                    color = state.JOB_STATE_TO_COLOR[status]
                    colors.append(QColor(*color))
            return colors
        elif role == RealLabelHint:
            return str(node.id)
        elif role == RealIens:
            return int(node.id)
        elif role == RealStatusColorHint:
            return QColor(*state.REAL_STATE_TO_COLOR[node.data[ids.STATUS]])
        else:
            return QVariant()

    def _job_data(self, index: QModelIndex, node: Node, role: int):
        if role == Qt.BackgroundRole:
            return QColor(
                *state.REAL_STATE_TO_COLOR[node.data.get(ids.STATUS)])
        if role == Qt.DisplayRole:
            _, data_name = COLUMNS[NodeType.STEP][index.column()]
            if data_name in [ids.CURRENT_MEMORY_USAGE, ids.MAX_MEMORY_USAGE]:
                data = node.data.get(ids.DATA)
                bytes = data.get(data_name) if data else None
                if bytes:
                    return byte_with_unit(bytes)
            if data_name in [ids.STDOUT, ids.STDERR]:
                return "OPEN" if node.data.get(data_name) else QVariant()
            if data_name in [ids.START_TIME, ids.END_TIME]:
                _time = node.data.get(data_name)
                if _time is not None:
                    return str(_time)
                return QVariant()
            return node.data.get(data_name)
        if role == FileRole:
            _, data_name = COLUMNS[NodeType.STEP][index.column()]
            if data_name in [ids.STDOUT, ids.STDERR]:
                return (node.data.get(data_name)
                        if node.data.get(data_name) else QVariant())
        if role == Qt.ToolTipRole:
            _, data_name = COLUMNS[NodeType.STEP][index.column()]
            if data_name in [ids.ERROR, ids.START_TIME, ids.END_TIME]:
                data = node.data.get(data_name)
                if data is not None:
                    return str(data)

        return QVariant()

    def index(self, row: int, column: int,
              parent=QModelIndex()) -> QModelIndex:
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parentItem = self.root
        else:
            parentItem = parent.internalPointer()

        childItem = None
        try:
            childItem = list(parentItem.children.values())[row]
        except KeyError:
            return QModelIndex()
        else:
            return self.createIndex(row, column, childItem)

    def reset(self):
        self.modelAboutToBeReset.emit()
        self.root = Node(None, {}, NodeType.ROOT)
        self.modelReset.emit()
Пример #3
0
class SnapshotModel(QAbstractItemModel):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.root = Node(None, {}, NodeType.ROOT)

    @staticmethod
    def prerender(
        snapshot: Union[Snapshot, PartialSnapshot]
    ) -> Union[Snapshot, PartialSnapshot]:
        """Pre-render some data that is required by this model. Ideally, this
        is called outside the GUI thread. This is a requirement of the model,
        so it has to be called."""

        # If there are no realizations, there's nothing to prerender.
        if not snapshot.data().get(ids.REALS):
            return

        metadata = {
            # A mapping from real to job to that job's QColor status representation
            REAL_JOB_STATUS_AGGREGATED: {},
            # A mapping from real to that real's QColor status representation
            REAL_STATUS_COLOR: {},
        }
        if isinstance(snapshot, Snapshot):
            metadata[SORTED_REALIZATION_IDS] = sorted(
                snapshot.data()[ids.REALS].keys(), key=int)
            for real_id, real in snapshot.data()[ids.REALS].items():
                for step in real[ids.STEPS].values():
                    metadata[SORTED_JOB_IDS] = sorted(step[ids.JOBS].keys(),
                                                      key=int)
                    break
                break

        for real_id, real in snapshot.data()[ids.REALS].items():
            if real.get(ids.STATUS):
                metadata[REAL_STATUS_COLOR][real_id] = _QCOLORS[
                    state.REAL_STATE_TO_COLOR[real[ids.STATUS]]]
            metadata[REAL_JOB_STATUS_AGGREGATED][real_id] = {}
            if real.get(ids.STEPS):
                for step in real[ids.STEPS].values():
                    if not ids.JOBS in step:
                        continue
                    for job_id in sorted(step[ids.JOBS].keys(), key=int):
                        status = step[ids.JOBS][job_id][ids.STATUS]
                        color = _QCOLORS[state.JOB_STATE_TO_COLOR[status]]
                        metadata[REAL_JOB_STATUS_AGGREGATED][real_id][
                            job_id] = color

        if isinstance(snapshot, Snapshot):
            snapshot.merge_metadata(metadata)
        elif isinstance(snapshot, PartialSnapshot):
            snapshot.update_metadata(metadata)
        return snapshot

    def _add_partial_snapshot(self, partial: PartialSnapshot, iter_: int):
        metadata = partial.data().get(ids.METADATA)
        if not metadata:
            logger.debug("no metadata in partial, ignoring partial")
            return

        if iter_ not in self.root.children:
            logger.debug("no full snapshot yet, ignoring partial")
            return

        if not partial.data().get(ids.REALS):
            logger.debug(f"no realizations in partial for iter {iter_}")
            return

        # Stack onto which we push change events for entities, since we branch
        # the code based on what is in the partial. This way we're guaranteed
        # that the change events will be emitted when the stack is unwound.
        with ExitStack() as stack:
            iter_node = self.root.children[iter_]
            iter_index = self.index(iter_node.row(), 0, QModelIndex())
            iter_index_bottom_right = self.index(iter_node.row(),
                                                 iter_index.column(),
                                                 QModelIndex())
            stack.callback(self.dataChanged.emit, iter_index,
                           iter_index_bottom_right)

            for real_id in iter_node.data[SORTED_REALIZATION_IDS]:
                real = partial.data()[ids.REALS].get(real_id)
                if not real:
                    continue
                real_node = iter_node.children[real_id]
                if real.get(ids.STATUS):
                    real_node.data[ids.STATUS] = real[ids.STATUS]

                real_index = self.index(real_node.row(), 0, iter_index)
                real_index_bottom_right = self.index(
                    real_node.row(),
                    self.columnCount(iter_index) - 1, iter_index)
                stack.callback(self.dataChanged.emit, real_index,
                               real_index_bottom_right)

                for job_id, color in (metadata[REAL_JOB_STATUS_AGGREGATED].get(
                        real_id, {}).items()):
                    real_node.data[REAL_JOB_STATUS_AGGREGATED][job_id] = color
                if real_id in metadata[REAL_STATUS_COLOR]:
                    real_node.data[REAL_STATUS_COLOR] = metadata[
                        REAL_STATUS_COLOR][real_id]

                if not real.get(ids.STEPS):
                    continue

                for step_id, step in real[ids.STEPS].items():
                    step_node = real_node.children[step_id]
                    if step.get(ids.STATUS):
                        step_node.data[ids.STATUS] = step[ids.STATUS]

                    step_index = self.index(step_node.row(), 0, real_index)

                    if not step.get(ids.JOBS):
                        continue

                    for job_id, job in step[ids.JOBS].items():
                        job_node = step_node.children[job_id]

                        job_index = self.index(job_node.row(), 0, step_index)
                        job_index_bottom_right = self.index(
                            job_node.row(),
                            self.columnCount() - 1, step_index)
                        stack.callback(self.dataChanged.emit, job_index,
                                       job_index_bottom_right)

                        if job.get(ids.STATUS):
                            job_node.data[ids.STATUS] = job[ids.STATUS]
                        if job.get(ids.START_TIME):
                            job_node.data[ids.START_TIME] = job[ids.START_TIME]
                        if job.get(ids.END_TIME):
                            job_node.data[ids.END_TIME] = job[ids.END_TIME]
                        if job.get(ids.STDOUT):
                            job_node.data[ids.STDOUT] = job[ids.STDOUT]
                        if job.get(ids.STDERR):
                            job_node.data[ids.STDERR] = job[ids.STDERR]

                        # Errors may be unset as the queue restarts the job
                        job_node.data[ids.ERROR] = (job[ids.ERROR] if job.get(
                            ids.ERROR) else "")

                        for attr in (ids.CURRENT_MEMORY_USAGE,
                                     ids.MAX_MEMORY_USAGE):
                            if job.get(ids.DATA) and attr in job.get(ids.DATA):
                                job_node.data[ids.DATA] = job_node.data[
                                    ids.DATA].set(attr,
                                                  job.get(ids.DATA).get(attr))

    def _add_snapshot(self, snapshot: Snapshot, iter_: int):
        # Parts of the metadata will be used in the underlying data model,
        # which is be mutable, hence we thaw it here—once.
        metadata = pyrsistent.thaw(snapshot.data()[ids.METADATA])
        snapshot_tree = Node(
            iter_,
            {
                ids.STATUS: snapshot.data()[ids.STATUS],
                SORTED_REALIZATION_IDS: metadata[SORTED_REALIZATION_IDS],
                SORTED_JOB_IDS: metadata[SORTED_JOB_IDS],
            },
            NodeType.ITER,
        )
        for real_id in snapshot_tree.data[SORTED_REALIZATION_IDS]:
            real = snapshot.data()[ids.REALS][real_id]
            real_node = Node(
                real_id,
                {
                    ids.STATUS:
                    real[ids.STATUS],
                    ids.ACTIVE:
                    real[ids.ACTIVE],
                    REAL_JOB_STATUS_AGGREGATED:
                    metadata[REAL_JOB_STATUS_AGGREGATED][real_id],
                    REAL_STATUS_COLOR:
                    metadata[REAL_STATUS_COLOR][real_id],
                },
                NodeType.REAL,
            )
            snapshot_tree.add_child(real_node)
            for step_id, step in real[ids.STEPS].items():
                step_node = Node(step_id, {ids.STATUS: step[ids.STATUS]},
                                 NodeType.STEP)
                real_node.add_child(step_node)
                for job_id in metadata[SORTED_JOB_IDS]:
                    job = step[ids.JOBS][job_id]
                    job_dict = dict(job)
                    job_dict[ids.DATA] = job.data
                    job_node = Node(job_id, job_dict, NodeType.JOB)
                    step_node.add_child(job_node)

        if iter_ in self.root.children:
            self.modelAboutToBeReset.emit()
            self.root.children[iter_] = snapshot_tree
            snapshot_tree.parent = self.root
            self.modelReset.emit()
            return

        parent = QModelIndex()
        next_iter = len(self.root.children)
        self.beginInsertRows(parent, next_iter, next_iter)
        self.root.add_child(snapshot_tree)
        self.root.children[iter_] = snapshot_tree
        self.rowsInserted.emit(parent, snapshot_tree.row(),
                               snapshot_tree.row())

    def columnCount(self, parent=QModelIndex()):
        parent_node = parent.internalPointer()
        if parent_node is None:
            return len(COLUMNS[NodeType.ROOT])
        return len(COLUMNS[parent_node.type])

    def rowCount(self, parent=QModelIndex()):
        if not parent.isValid():
            parentItem = self.root
        else:
            parentItem = parent.internalPointer()

        if parent.column() > 0:
            return 0

        return len(parentItem.children)

    def parent(self, index: QModelIndex):
        if not index.isValid():
            return QModelIndex()

        child_item = index.internalPointer()
        if not hasattr(child_item, "parent"):
            raise ValueError(
                f"index r{index.row()}/c{index.column()} pointed to parent-less item {child_item}"
            )
        parentItem = child_item.parent

        if parentItem == self.root:
            return QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    def data(self, index: QModelIndex, role=Qt.DisplayRole):
        if not index.isValid():
            return QVariant()

        if role == Qt.TextAlignmentRole:
            return Qt.AlignCenter

        node = index.internalPointer()

        if role == NodeRole:
            return node

        if node.type == NodeType.JOB:
            return self._job_data(index, node, role)
        elif node.type == NodeType.REAL:
            return self._real_data(index, node, role)

        if role == Qt.DisplayRole:
            if index.column() == 0:
                return f"{node.type}:{node.id}"
            if index.column() == 1:
                return f"{node.data['status']}"

        if role in (Qt.StatusTipRole, Qt.WhatsThisRole, Qt.ToolTipRole):
            return ""

        if role == Qt.SizeHintRole:
            return QSize()

        if role == Qt.FontRole:
            return QFont()

        if role in (Qt.BackgroundRole, Qt.ForegroundRole, Qt.DecorationRole):
            return QColor()

        return QVariant()

    def _real_data(self, index: QModelIndex, node: Node, role: int):
        if role == RealJobColorHint:
            colors: List[QColor] = []
            for job_id in node.parent.data[SORTED_JOB_IDS]:
                colors.append(node.data[REAL_JOB_STATUS_AGGREGATED][job_id])
            return colors
        elif role == RealLabelHint:
            return node.id
        elif role == RealIens:
            return node.id
        elif role == RealStatusColorHint:
            return node.data[REAL_STATUS_COLOR]
        else:
            return QVariant()

    def _job_data(self, index: QModelIndex, node: Node, role: int):
        if role == Qt.BackgroundRole:
            real = node.parent.parent
            return real.data[REAL_JOB_STATUS_AGGREGATED][node.id]
        if role == Qt.DisplayRole:
            _, data_name = COLUMNS[NodeType.STEP][index.column()]
            if data_name in [ids.CURRENT_MEMORY_USAGE, ids.MAX_MEMORY_USAGE]:
                data = node.data.get(ids.DATA)
                _bytes = data.get(data_name) if data else None
                if _bytes:
                    return byte_with_unit(_bytes)
            if data_name in [ids.STDOUT, ids.STDERR]:
                return "OPEN" if node.data.get(data_name) else QVariant()
            if data_name in [DURATION]:
                start_time = node.data.get(ids.START_TIME)
                if start_time is None:
                    return QVariant()
                delta = _estimate_duration(start_time,
                                           end_time=node.data.get(
                                               ids.END_TIME))
                # There is no method for truncating microseconds, so we remove them
                delta -= datetime.timedelta(microseconds=delta.microseconds)
                return str(delta)
            return node.data.get(data_name)
        if role == FileRole:
            _, data_name = COLUMNS[NodeType.STEP][index.column()]
            if data_name in [ids.STDOUT, ids.STDERR]:
                return (node.data.get(data_name)
                        if node.data.get(data_name) else QVariant())
        if role == Qt.ToolTipRole:
            _, data_name = COLUMNS[NodeType.STEP][index.column()]
            data = None
            if data_name == ids.ERROR:
                data = node.data.get(data_name)
            elif data_name == DURATION:
                start_time = node.data.get(ids.START_TIME)
                if start_time is not None:
                    delta = _estimate_duration(start_time,
                                               end_time=node.data.get(
                                                   ids.END_TIME))
                    data = f"Start time: {str(start_time)}\nDuration: {str(delta)}"
            if data is not None:
                return str(data)

        return QVariant()

    def index(self, row: int, column: int,
              parent=QModelIndex()) -> QModelIndex:
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parentItem = self.root
        else:
            parentItem = parent.internalPointer()

        childItem = None
        try:
            childItem = list(parentItem.children.values())[row]
        except KeyError:
            return QModelIndex()
        else:
            return self.createIndex(row, column, childItem)

    def reset(self):
        self.modelAboutToBeReset.emit()
        self.root = Node(None, {}, NodeType.ROOT)
        self.modelReset.emit()
Пример #4
0
 def reset(self):
     self.modelAboutToBeReset.emit()
     self.root = Node(None, {}, NodeType.ROOT)
     self.modelReset.emit()
Пример #5
0
    def _add_snapshot(self, snapshot: Snapshot, iter_: int):
        # Parts of the metadata will be used in the underlying data model,
        # which is be mutable, hence we thaw it here—once.
        metadata = pyrsistent.thaw(snapshot.data()[ids.METADATA])
        snapshot_tree = Node(
            iter_,
            {
                ids.STATUS: snapshot.data()[ids.STATUS],
                SORTED_REALIZATION_IDS: metadata[SORTED_REALIZATION_IDS],
                SORTED_JOB_IDS: metadata[SORTED_JOB_IDS],
            },
            NodeType.ITER,
        )
        for real_id in snapshot_tree.data[SORTED_REALIZATION_IDS]:
            real = snapshot.data()[ids.REALS][real_id]
            real_node = Node(
                real_id,
                {
                    ids.STATUS:
                    real[ids.STATUS],
                    ids.ACTIVE:
                    real[ids.ACTIVE],
                    REAL_JOB_STATUS_AGGREGATED:
                    metadata[REAL_JOB_STATUS_AGGREGATED][real_id],
                    REAL_STATUS_COLOR:
                    metadata[REAL_STATUS_COLOR][real_id],
                },
                NodeType.REAL,
            )
            snapshot_tree.add_child(real_node)
            for step_id, step in real[ids.STEPS].items():
                step_node = Node(step_id, {ids.STATUS: step[ids.STATUS]},
                                 NodeType.STEP)
                real_node.add_child(step_node)
                for job_id in metadata[SORTED_JOB_IDS]:
                    job = step[ids.JOBS][job_id]
                    job_dict = dict(job)
                    job_dict[ids.DATA] = job.data
                    job_node = Node(job_id, job_dict, NodeType.JOB)
                    step_node.add_child(job_node)

        if iter_ in self.root.children:
            self.modelAboutToBeReset.emit()
            self.root.children[iter_] = snapshot_tree
            snapshot_tree.parent = self.root
            self.modelReset.emit()
            return

        parent = QModelIndex()
        next_iter = len(self.root.children)
        self.beginInsertRows(parent, next_iter, next_iter)
        self.root.add_child(snapshot_tree)
        self.root.children[iter_] = snapshot_tree
        self.rowsInserted.emit(parent, snapshot_tree.row(),
                               snapshot_tree.row())