Author: Armin Rigo <ar...@tunes.org>
Branch: reverse-debugger
Changeset: r85232:7b34a47bb51b
Date: 2016-06-20 09:27 +0200
http://bitbucket.org/pypy/pypy/changeset/7b34a47bb51b/

Log:    in-progress

diff --git a/rpython/translator/revdb/interact.py 
b/rpython/translator/revdb/interact.py
--- a/rpython/translator/revdb/interact.py
+++ b/rpython/translator/revdb/interact.py
@@ -27,14 +27,26 @@
         child = ReplayProcess(subproc.pid, s1)
         msg = child.expect(ANSWER_INIT, INIT_VERSION_NUMBER, Ellipsis)
         self.total_stop_points = msg.arg2
-        child.expect(ANSWER_STD, 1, Ellipsis)
+        msg = child.expect(ANSWER_STD, 1, Ellipsis)
+        child.update_times(msg)
         self.active_child = child
-        self.paused_children = {}
+        self.paused_children = []
+        #
+        # fixed time division for now
+        if self.total_stop_points < 1:
+            raise ValueError("no stop points recorded in %r" % (
+                revdb_log_filename,))
+        self.fork_points = {1: child.clone()}
+        p = 1
+        while p < self.total_stop_points and len(self.fork_points) < 30:
+            p += int((self.total_stop_points - p) * 0.25) + 1
+            self.fork_points[p] = None
 
     def interact(self):
         last_command = ''
         while True:
-            prompt = '(%d)$ ' % self.active_child.current_time()
+            last_time = self.active_child.current_time
+            prompt = '(%d)$ ' % last_time
             try:
                 cmdline = raw_input(prompt).strip()
             except EOFError:
@@ -54,8 +66,7 @@
                 try:
                     runner(argument)
                 except Exception as e:
-                    for line in traceback.format_exception_only(type(e), e):
-                        sys.stderr.write(line)
+                    traceback.print_exc()
                 last_command = cmdline
 
     def command_help(self, argument):
@@ -68,9 +79,26 @@
                 print '\t%s\t%s' % (command, docstring)
 
     def command_quit(self, argument):
-        """Exit the reverse debugger"""
+        """Exit the debugger"""
         sys.exit(0)
 
+    def change_child(self, target_time):
+        """If 'target_time' is not soon after 'self.active_child',
+        close it and fork another child."""
+        assert 1 <= target_time <= self.total_stop_points
+        best = max([key for key in self.fork_points if key <= target_time])
+        if best < self.active_child.current_time <= target_time:
+            pass     # keep 'active_child'
+        else:
+            self.active_child.close()
+            self.active_child = self.fork_points[best].clone()
+
     def command_go(self, argument):
         """Go to time ARG"""
         target_time = int(argument)
+        if target_time < 1:
+            target_time = 1
+        if target_time > self.total_stop_points:
+            target_time = self.total_stop_points
+        self.change_child(target_time)
+        self.active_child.forward(target_time - self.active_child.current_time)
diff --git a/rpython/translator/revdb/message.py 
b/rpython/translator/revdb/message.py
--- a/rpython/translator/revdb/message.py
+++ b/rpython/translator/revdb/message.py
@@ -1,8 +1,5 @@
-import os, struct, socket, errno
-from rpython.translator.revdb import ancillary
 
-
-INIT_VERSION_NUMBER   = 0xd80100
+INIT_VERSION_NUMBER = 0xd80100
 
 CMD_FORK     = -1
 CMD_QUIT     = -2
@@ -15,6 +12,10 @@
 
 
 class Message(object):
+    """Represent messages sent and received to subprocesses
+    started with --revdb-replay.
+    """
+
     def __init__(self, cmd, arg1=0, arg2=0, arg3=0, extra=""):
         self.cmd = cmd
         self.arg1 = arg1
@@ -36,65 +37,3 @@
 
     def __ne__(self, other):
         return not (self == other)
-
-
-class ReplayProcess(object):
-    def __init__(self, pid, control_socket):
-        self.pid = pid
-        self.control_socket = control_socket
-
-    def _recv_all(self, size):
-        pieces = []
-        while size > 0:
-            data = self.control_socket.recv(size)
-            if not data:
-                raise EOFError
-            size -= len(data)
-            pieces.append(data)
-        return ''.join(pieces)
-
-    def send(self, msg):
-        binary = struct.pack("iIqqq", msg.cmd, len(msg.extra),
-                             msg.arg1, msg.arg2, msg.arg3)
-        self.control_socket.sendall(binary + msg.extra)
-
-    def recv(self):
-        binary = self._recv_all(struct.calcsize("iIqqq"))
-        cmd, size, arg1, arg2, arg3 = struct.unpack("iIqqq", binary)
-        extra = self._recv_all(size)
-        return Message(cmd, arg1, arg2, arg3, extra)
-
-    def expect(self, cmd, arg1=0, arg2=0, arg3=0, extra=""):
-        msg = self.recv()
-        assert msg.cmd == cmd
-        if arg1 is not Ellipsis:
-            assert msg.arg1 == arg1
-        if arg2 is not Ellipsis:
-            assert msg.arg2 == arg2
-        if arg3 is not Ellipsis:
-            assert msg.arg3 == arg3
-        if extra is not Ellipsis:
-            assert msg.extra == extra
-        return msg
-
-    def fork(self):
-        self.send(Message(CMD_FORK))
-        s1, s2 = socket.socketpair()
-        ancillary.send_fds(self.control_socket.fileno(), [s2.fileno()])
-        s2.close()
-        msg = self.expect(ANSWER_FORKED, Ellipsis)
-        child_pid = msg.arg1
-        return ReplayProcess(child_pid, s1)
-
-    def close(self):
-        self.send(Message(CMD_QUIT))
-
-    def forward(self, steps):
-        self.send(Message(CMD_FORWARD, steps))
-        return self.expect(ANSWER_STD, Ellipsis, Ellipsis)
-
-    def current_time(self):
-        return self.forward(0).arg1
-
-    def currently_created_objects(self):
-        return self.forward(0).arg2
diff --git a/rpython/translator/revdb/process.py 
b/rpython/translator/revdb/process.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/revdb/process.py
@@ -0,0 +1,168 @@
+import os, struct, socket, errno, subprocess
+from rpython.translator.revdb import ancillary
+from rpython.translator.revdb.message import *
+
+
+class ReplayProcess(object):
+    """Represent one replaying subprocess.
+
+    It can be either the one started with --revdb-replay, or a fork.
+    """
+
+    def __init__(self, pid, control_socket):
+        self.pid = pid
+        self.control_socket = control_socket
+
+    def _recv_all(self, size):
+        pieces = []
+        while size > 0:
+            data = self.control_socket.recv(size)
+            if not data:
+                raise EOFError
+            size -= len(data)
+            pieces.append(data)
+        return ''.join(pieces)
+
+    def send(self, msg):
+        binary = struct.pack("iIqqq", msg.cmd, len(msg.extra),
+                             msg.arg1, msg.arg2, msg.arg3)
+        self.control_socket.sendall(binary + msg.extra)
+
+    def recv(self):
+        binary = self._recv_all(struct.calcsize("iIqqq"))
+        cmd, size, arg1, arg2, arg3 = struct.unpack("iIqqq", binary)
+        extra = self._recv_all(size)
+        return Message(cmd, arg1, arg2, arg3, extra)
+
+    def expect(self, cmd, arg1=0, arg2=0, arg3=0, extra=""):
+        msg = self.recv()
+        assert msg.cmd == cmd
+        if arg1 is not Ellipsis:
+            assert msg.arg1 == arg1
+        if arg2 is not Ellipsis:
+            assert msg.arg2 == arg2
+        if arg3 is not Ellipsis:
+            assert msg.arg3 == arg3
+        if extra is not Ellipsis:
+            assert msg.extra == extra
+        return msg
+
+    def update_times(self, msg):
+        self.current_time = msg.arg1
+        self.currently_created_objects = msg.arg2
+
+    def clone(self):
+        """Fork this subprocess.  Returns a new ReplayProcess() that is
+        an identical copy.
+        """
+        self.send(Message(CMD_FORK))
+        s1, s2 = socket.socketpair()
+        ancillary.send_fds(self.control_socket.fileno(), [s2.fileno()])
+        s2.close()
+        msg = self.expect(ANSWER_FORKED, Ellipsis, Ellipsis, Ellipsis)
+        self.update_times(msg)
+        child_pid = msg.arg3
+        other = ReplayProcess(child_pid, s1)
+        other.update_times(msg)
+        return other
+
+    def close(self):
+        """Close this subprocess."""
+        self.send(Message(CMD_QUIT))
+
+    def forward(self, steps):
+        """Move this subprocess forward in time."""
+        self.send(Message(CMD_FORWARD, steps))
+        msg = self.expect(ANSWER_STD, Ellipsis, Ellipsis)
+        self.update_times(msg)
+        return msg
+
+
+class ReplayProcessGroup(object):
+    """Handle a family of subprocesses.
+    """
+    MAX_SUBPROCESSES = 31       # maximum number of subprocesses
+    STEP_RATIO = 0.25           # subprocess n is between subprocess n-1
+                                #   and the end, at this time fraction
+
+    def __init__(self, executable, revdb_log_filename):
+        s1, s2 = socket.socketpair()
+        initial_subproc = subprocess.Popen(
+            [executable, '--revdb-replay', revdb_log_filename,
+             str(s2.fileno())])
+        s2.close()
+        child = ReplayProcess(initial_subproc.pid, s1)
+        msg = child.expect(ANSWER_INIT, INIT_VERSION_NUMBER, Ellipsis)
+        self.total_stop_points = msg.arg2
+        msg = child.expect(ANSWER_STD, 1, Ellipsis)
+        child.update_times(msg)
+
+        self.active = child
+        self.paused = {1: child.clone()}     # {time: subprocess}
+
+    def get_current_time(self):
+        return self.active.current_time
+
+    def _check_current_time(self, time):
+        assert self.get_current_time() == time
+        self.active.send(Message(CMD_FORWARD, 0))
+        return self.active.expect(ANSWER_STD, time, Ellipsis)
+
+    def get_max_time(self):
+        return self.total_stop_points
+
+    def get_next_clone_time(self):
+        if len(self.paused) >= self.MAX_SUBPROCESSES:
+            next_time = self.total_stop_points + 1
+        else:
+            latest_done = max(self.paused)
+            range_not_done = self.total_stop_points - latest_done
+            next_time = latest_done + int(self.STEP_RATIO * range_not_done) + 1
+        return next_time
+
+    def forward(self, steps):
+        """Go forward, for the given number of 'steps' of time.
+
+        If needed, it will leave clones at intermediate times.
+        Does not close the active subprocess.
+        """
+        assert steps >= 0
+        while True:
+            cur_time = self.get_current_time()
+            if cur_time + steps > self.total_stop_points:
+                steps = self.total_stop_points - cur_time
+            next_clone = self.get_next_clone_time()
+            rel_next_clone = next_clone - cur_time
+            if rel_next_clone > steps:
+                break
+            assert rel_next_clone >= 0
+            if rel_next_clone > 0:
+                self.active.forward(rel_next_clone)
+                steps -= rel_next_clone
+            clone = self.active.clone()
+            self.paused[clone.current_time] = clone
+        self.active.forward(steps)
+
+    def _resume(self, from_time):
+        clone_me = self.paused[from_time]
+        self.active.close()
+        self.active = clone_me.clone()
+
+    def jump_in_time(self, target_time):
+        """Jump in time at the given 'target_time'.
+
+        This function can close the active subprocess.
+        """
+        if target_time < 1:
+            target_time = 1
+        if target_time > self.total_stop_points:
+            target_time = self.total_stop_points
+
+        cur_time = self.get_current_time()
+        if target_time >= cur_time:    # can go forward
+            if cur_time >= max(self.paused):  # current time is past all forks
+                self.forward(target_time - cur_time)
+                return
+        # else, start from a fork
+        self._resume(max(time for time in self.paused if time <= target_time))
+        self.forward(target_time - self.get_current_time())
diff --git a/rpython/translator/revdb/revdb.py 
b/rpython/translator/revdb/revdb.py
--- a/rpython/translator/revdb/revdb.py
+++ b/rpython/translator/revdb/revdb.py
@@ -10,7 +10,7 @@
     parser.add_argument('-x', '--executable', dest='executable',
                         help='name of the executable file '
                              'that recorded the log')
-    options = parser.parse_args(sys.argv[1:])
+    options = parser.parse_args()
 
     sys.path.insert(0, os.path.abspath(
         os.path.join(__file__, '..', '..', '..', '..')))
diff --git a/rpython/translator/revdb/src-revdb/revdb.c 
b/rpython/translator/revdb/src-revdb/revdb.c
--- a/rpython/translator/revdb/src-revdb/revdb.c
+++ b/rpython/translator/revdb/src-revdb/revdb.c
@@ -282,8 +282,9 @@
 
 static void answer_std(void)
 {
-    write_answer(ANSWER_STD, rpy_revdb.stop_point_seen,
-                 rpy_revdb.unique_id_seen, 0);
+    assert(stopped_time != 0);
+    assert(stopped_uid != 0);
+    write_answer(ANSWER_STD, stopped_time, stopped_uid, 0);
 }
 
 static RPyString *make_rpy_string(size_t length)
@@ -516,7 +517,7 @@
     }
     else {
         /* in the parent */
-        write_answer(ANSWER_FORKED, child_pid, 0, 0);
+        write_answer(ANSWER_FORKED, stopped_time, stopped_uid, child_pid);
         close(child_sockfd);
     }
 }
@@ -593,6 +594,7 @@
                 break;
             }
         }
+        rpy_revdb.stop_point_seen = stopped_time;
         rpy_revdb.unique_id_seen = stopped_uid;
         stopped_time = 0;
         stopped_uid = 0;
diff --git a/rpython/translator/revdb/test/test_basic.py 
b/rpython/translator/revdb/test/test_basic.py
--- a/rpython/translator/revdb/test/test_basic.py
+++ b/rpython/translator/revdb/test/test_basic.py
@@ -10,6 +10,7 @@
 from rpython.rtyper.lltypesystem import lltype, llmemory
 
 from rpython.translator.revdb.message import *
+from rpython.translator.revdb.process import ReplayProcess
 
 
 class RDB(object):
diff --git a/rpython/translator/revdb/test/test_process.py 
b/rpython/translator/revdb/test/test_process.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/revdb/test/test_process.py
@@ -0,0 +1,49 @@
+from rpython.rlib import revdb
+from rpython.translator.revdb.message import *
+from rpython.translator.revdb.process import ReplayProcessGroup
+
+from hypothesis import given, strategies
+
+
+class TestReplayProcessGroup:
+
+    def setup_class(cls):
+        from rpython.translator.revdb.test.test_basic import compile, run
+
+        class Stuff:
+            pass
+
+        class DBState:
+            pass
+        dbstate = DBState()
+
+        def main(argv):
+            for i, op in enumerate(argv[1:]):
+                dbstate.stuff = Stuff()
+                dbstate.stuff.x = i + 1000
+                revdb.stop_point()
+                print op
+            return 9
+        compile(cls, main, [], backendopt=False)
+        assert run(cls, 'abc d ef g h i j k l m') == (
+            'abc\nd\nef\ng\nh\ni\nj\nk\nl\nm\n')
+
+
+    def test_init(self):
+        group = ReplayProcessGroup(str(self.exename), self.rdbname)
+        assert group.get_max_time() == 10
+        assert group.get_next_clone_time() == 4
+
+    def test_forward(self):
+        group = ReplayProcessGroup(str(self.exename), self.rdbname)
+        group.forward(100)
+        assert group.get_current_time() == 10
+        assert sorted(group.paused) == [1, 4, 6, 8, 9, 10]
+        assert group._check_current_time(10)
+
+    @given(strategies.lists(strategies.integers(min_value=1, max_value=10)))
+    def test_jump_in_time(self, target_times):
+        group = ReplayProcessGroup(str(self.exename), self.rdbname)
+        for target_time in target_times:
+            group.jump_in_time(target_time)
+            group._check_current_time(target_time)
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to