]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/pmdk/src/tools/pmreorder/statemachine.py
import ceph 16.2.7
[ceph.git] / ceph / src / pmdk / src / tools / pmreorder / statemachine.py
diff --git a/ceph/src/pmdk/src/tools/pmreorder/statemachine.py b/ceph/src/pmdk/src/tools/pmreorder/statemachine.py
new file mode 100644 (file)
index 0000000..9df52c3
--- /dev/null
@@ -0,0 +1,364 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018-2020, Intel Corporation
+
+import memoryoperations as memops
+import reorderengines
+from reorderexceptions import InconsistentFileException
+from reorderexceptions import NotSupportedOperationException
+
+
+class State:
+    """
+    The base class of all states.
+
+    :ivar _context: The reordering context.
+    :type _context: opscontext.OpsContext
+    :ivar trans_stores: The list of unflushed stores.
+    :type trans_stores: list of :class:`memoryoperations.Store`
+    """
+    trans_stores = []
+
+    def __init__(self, context):
+        """
+        Default state constructor.
+
+        :param context: The context of the reordering.
+        :type context: opscontext.OpsContext
+        """
+        self._context = context
+
+    def next(self, in_op):
+        """
+        Go to the next state based on the input.
+
+        :Note:
+            The next state might in fact be the same state.
+
+        :param in_op: The state switch trigger operation.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: The next state.
+        :rtype: subclass of :class:`State`
+        """
+        raise NotImplementedError
+
+    def run(self, in_op):
+        """
+        Perform the required operation in this state.
+
+        :param in_op: The operation to be performed in this state.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: None
+        """
+        raise NotImplementedError
+
+
+class InitState(State):
+    """
+    The initial no-op state.
+    """
+    def __init__(self, context):
+        """
+        Saves the reordering context.
+
+        :param context: The reordering context.
+        :type context: opscontext.OpsContext
+        """
+        super(InitState, self).__init__(context)
+
+    def next(self, in_op):
+        """
+        Switch to the next valid state.
+
+        :param in_op: Ignored.
+        :return: The next valid state.
+        :rtype: CollectingState
+        """
+        return CollectingState(self._context)
+
+    def run(self, in_op):
+        """
+        Does nothing.
+
+        :param in_op: Ignored.
+        :return: always True
+        """
+        return True
+
+
+class CollectingState(State):
+    """
+    Collects appropriate operations.
+
+    This state mostly aggregates stores and flushes. It also
+    validates which stores will be made persistent and passes
+    them on to the next state.
+
+    :ivar _ops_list: The list of collected stores.
+    :type _ops_list: list of :class:`memoryoperations.Store`
+    :ivar _inner_state: The internal state of operations.
+    :type _inner_state: str
+    """
+    def __init__(self, context):
+        """
+        Saves the reordering context.
+
+        :param context: The reordering context.
+        :type context: opscontext.OpsContext
+        """
+        super(CollectingState, self).__init__(context)
+        self._ops_list = []
+        self._ops_list += State.trans_stores
+        self._inner_state = "init"
+
+    def next(self, in_op):
+        """
+        Switch to the next valid state.
+
+        :param in_op: The state switch trigger operation.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: The next state.
+        :rtype: subclass of :class:`State`
+        """
+        if isinstance(in_op, memops.Fence) and \
+                self._inner_state == "flush":
+            return ReplayingState(self._ops_list, self._context)
+        else:
+            return self
+
+    def run(self, in_op):
+        """
+        Perform operations in this state.
+
+        Based on the type of operation, different handling is employed.
+        The recognized and handled types of operations are:
+
+            * :class:`memoryoperations.ReorderBase`
+            * :class:`memoryoperations.FlushBase`
+            * :class:`memoryoperations.Store`
+            * :class:`memoryoperations.Register_file`
+
+        :param in_op: The operation to be performed in this state.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: always True
+        """
+        self.move_inner_state(in_op)
+        if isinstance(in_op, memops.ReorderBase):
+            self.substitute_reorder(in_op)
+        elif isinstance(in_op, memops.FlushBase):
+            self.flush_stores(in_op)
+        elif isinstance(in_op, memops.Store):
+            self._ops_list.append(in_op)
+        elif isinstance(in_op, memops.Register_file):
+            self.reg_file(in_op)
+
+        return True
+
+    def substitute_reorder(self, order_ops):
+        """
+        Changes the reordering engine based on the log marker class.
+
+        :param order_ops: The reordering marker class.
+        :type order_ops: subclass of :class:`memoryoperations.ReorderBase`
+        :return: None
+        """
+        if isinstance(order_ops, memops.ReorderFull):
+            self._context.reorder_engine = \
+                reorderengines.FullReorderEngine()
+            self._context.test_on_barrier = \
+                self._context.reorder_engine.test_on_barrier
+        elif isinstance(order_ops, memops.ReorderPartial):
+            # TODO add macro in valgrind or
+            # parameter inside the tool to support parameters?
+            self._context.reorder_engine = \
+                 reorderengines.RandomPartialReorderEngine(3)
+            self._context.test_on_barrier = \
+                self._context.reorder_engine.test_on_barrier
+        elif isinstance(order_ops, memops.ReorderAccumulative):
+            self._context.reorder_engine = \
+                reorderengines.AccumulativeReorderEngine()
+            self._context.test_on_barrier = \
+                self._context.reorder_engine.test_on_barrier
+        elif isinstance(order_ops, memops.ReorderReverseAccumulative):
+            self._context.reorder_engine = \
+                reorderengines.AccumulativeReverseReorderEngine()
+            self._context.test_on_barrier = \
+                self._context.reorder_engine.test_on_barrier
+        elif isinstance(order_ops, memops.NoReorderDoCheck):
+            self._context.reorder_engine = reorderengines.NoReorderEngine()
+            self._context.test_on_barrier = \
+                self._context.reorder_engine.test_on_barrier
+        elif isinstance(order_ops, memops.NoReorderNoCheck):
+            self._context.reorder_engine = reorderengines.NoCheckerEngine()
+            self._context.test_on_barrier = \
+                self._context.reorder_engine.test_on_barrier
+        elif isinstance(order_ops, memops.ReorderDefault):
+            self._context.reorder_engine = self._context.default_engine
+            self._context.test_on_barrier = self._context.default_barrier
+        else:
+            raise NotSupportedOperationException(
+                                   "Not supported reorder engine: {}"
+                                   .format(order_ops))
+
+    def flush_stores(self, flush_op):
+        """
+        Marks appropriate stores as flushed.
+
+        Does not align the flush, the log is expected to have the
+        flushes properly aligned.
+
+        :param flush_op: The flush operation marker.
+        :type flush_op: subclass of :class:`memoryoperations.FlushBase`
+        :return: None
+        """
+        for st in self._ops_list:
+            if flush_op.is_in_flush(st):
+                st.flushed = True
+
+    def reg_file(self, file_op):
+        """
+        Register a new file mapped into virtual memory.
+
+        :param file_op: File registration operation marker.
+        :type file_op: memoryoperations.Register_file
+        :return: None
+        """
+        self._context.file_handler.add_file(file_op.name,
+                                            file_op.address,
+                                            file_op.size)
+
+    def move_inner_state(self, in_op):
+        """
+        Tracks the internal state of the collection.
+
+        The collected stores need to be processed only at specific moments -
+        after full persistent memory barriers (flush-fence).
+
+        :param in_op: The performed operation.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: None
+        """
+        if isinstance(in_op, memops.Store) and \
+                self._inner_state == "init":
+            self._inner_state = "dirty"
+        elif isinstance(in_op, memops.FlushBase) and \
+                self._inner_state == "dirty":
+            self._inner_state = "flush"
+        elif isinstance(in_op, memops.Fence) and \
+                self._inner_state == "flush":
+            self._inner_state = "fence"
+        elif isinstance(in_op, memops.Flush) and \
+                self._inner_state == "init":
+            self._inner_state = "flush"
+
+
+class ReplayingState(State):
+    """
+    Replays all collected stores according to the reordering context.
+
+    :ivar _ops_list: The list of stores to be reordered and replayed.
+    :type _ops_list: list of :class:`memoryoperations.Store`
+    """
+    def __init__(self, in_ops_list, context):
+        """
+
+        :param in_ops_list:
+        :param context:
+        :return:
+        """
+        super(ReplayingState, self).__init__(context)
+        self._ops_list = in_ops_list
+
+    def next(self, in_op):
+        """
+        Switches to the collecting state regardless of the input.
+
+        :param in_op: Ignored.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: The next state.
+        :rtype: CollectingState
+        """
+        return CollectingState(self._context)
+
+    def run(self, in_op):
+        """
+        Perform operations in this state.
+
+        The replaying state performs reordering and if necessary checks
+        the consistency of the registered files. The decisions and
+        type of reordering to be used is defined by the context.
+
+        :param in_op: The operation to be performed in this state.
+        :type in_op: subclass of :class:`memoryoperations.BaseOperation`
+        :return: State of consistency check.
+        """
+        # specifies consistency state of sequence
+        consistency = True
+
+        # consider only flushed stores
+        flushed_stores = list(filter(lambda x: x.flushed, self._ops_list))
+
+        # not-flushed stores should be passed to next state
+        State.trans_stores = list(filter(lambda x: x.flushed is False,
+                                         self._ops_list))
+
+        if self._context.test_on_barrier:
+            for seq in self._context.reorder_engine.generate_sequence(
+                                                              flushed_stores):
+                for op in seq:
+                    # do stores
+                    self._context.file_handler.do_store(op)
+                # check consistency of all files
+                try:
+                    self._context.file_handler.check_consistency()
+                except InconsistentFileException as e:
+                    consistency = False
+                    self._context.logger.warning(e)
+                    stacktrace = "Call trace:\n"
+                    for num, op in enumerate(seq):
+                        stacktrace += "Store [{}]:\n".format(num)
+                        stacktrace += str(op.trace)
+                    self._context.logger.warning(stacktrace)
+
+                for op in reversed(seq):
+                    # revert the changes
+                    self._context.file_handler.do_revert(op)
+        # write all flushed stores
+        for op in flushed_stores:
+            self._context.file_handler.do_store(op)
+
+        return consistency
+
+
+class StateMachine:
+    """
+    The state machine driver.
+
+    :ivar _curr_state: The current state.
+    :type _curr_state: subclass of :class:`State`
+    """
+    def __init__(self, init_state):
+        """
+        Initialize the state machine with a specified state.
+
+        :param init_state: The initial state to be used.
+        :type init_state: subclass of :class:`State`
+        """
+        self._curr_state = init_state
+
+    def run_all(self, operations):
+        """
+        Starts the state machine.
+
+        :param operations: The operations to be performed by the state
+            machine.
+        :type operations: list of :class:`memoryoperations.BaseOperation`
+        :return: None
+        """
+        all_consistent = True
+        for ops in operations:
+            self._curr_state = self._curr_state.next(ops)
+            check = self._curr_state.run(ops)
+            if check is False:
+                all_consistent = check
+
+        return all_consistent