Skip to content

Conversation

@silverjam
Copy link
Contributor

@silverjam silverjam commented Feb 27, 2019

This is paired with the following piksi_tools PR: swift-nav/piksi_tools#985

Design notes

Numba based parsing for SBP classes. Adds various numba accelerated get_* functions used to parse SBP data.

Parsing floats

Floats are parsed using a module generated by another library, cffi -- https://cffi.readthedocs.io/en/latest/ -- this is used to generate a Python native module that can parse and re-assemble float and double values from a stream of bytes. This was done because numba was unable to generate code that could do the necessary bit banging.

To generate this module, run the following:

source <my/libsbp/virtualenv/bin/activate>
cd python/sbp/jit
python parse_float.py

This will generate parse_float_c.so (and parse_float_c.c) which will be loadable by Python.

Next steps

The next step from here would be to make the various MsgWhatever.parse_members methods able to be JIT generated. This would require some more clever use of Tuple(( ... )) for the return values of the .parse_member method.

Testing results

  • Without JIT’d unpack/parse (with JIT’d crc routine though):

    $ NOJIT=y time python ./sbp2json.py <sbp16m.bin  >/dev/null
       247.10 real       227.52 user         1.40 sys
    
  • With JIT’d crc/unpack/parse:

    $ time python ./sbp2json.py <sbp16m.bin  >/dev/null
    
    real    0m11.632s 
    user    0m10.813s
    sys     0m0.338s
    
  • Haskell sbp2json for reference:

    $ time sbp2json <sbp16m.bin  >/dev/null
    
    real    0m6.563s
    user    0m8.168s
    sys     0m0.667s
    

@silverjam silverjam changed the title Python: Numba base SBP parsing [WIP] Python: Numba base SBP parsing Feb 27, 2019
@silverjam silverjam changed the title [WIP] Python: Numba base SBP parsing [WIP] Python: Numba based SBP parsing Feb 28, 2019
@pmiettinen
Copy link
Contributor

make python failure on Ubuntu 16.04. Rebased on top of master.

========================================================================================== test session starts ==========================================================================================
platform linux2 -- Python 2.7.12, pytest-3.1.2, py-1.8.0, pluggy-0.4.0 -- /mnt/users/pasi/swiftnav/libsbp/python/.tox/py27/bin/python2.7
cachedir: .cache
rootdir: /mnt/users/pasi/swiftnav/libsbp/python, inifile: pytest.ini
plugins: cov-2.5.1
collected 32 items

tests/sbp/test_messages.py::test_messages PASSED
tests/sbp/test_numba.py::test_get_string PASSED
tests/sbp/test_numba.py::test_get_string_no_null PASSED
tests/sbp/test_numba.py::test_get_string_offset_no_null PASSED
tests/sbp/test_numba.py::test_get_string_offset PASSED
tests/sbp/test_numba.py::test_get_fixed_string_offset PASSED
tests/sbp/test_numba.py::test_parse PASSED
tests/sbp/test_numba.py::test_jit PASSED
tests/sbp/test_table.py::test_table_count PASSED
tests/sbp/test_table.py::test_table_unqiue_count PASSED
tests/sbp/test_table.py::test_available_messages PASSED
tests/sbp/client/test_driver.py::test_http_test_pass <- .tox/py27/local/lib/python2.7/site-packages/httpretty/core.py FAILED
tests/sbp/client/test_driver.py::test_http_test_fail <- .tox/py27/local/lib/python2.7/site-packages/httpretty/core.py FAILED
tests/sbp/client/test_driver.py::test_http_test_pass_streaming <- .tox/py27/local/lib/python2.7/site-packages/httpretty/core.py FAILED
tests/sbp/client/test_driver.py::test_http_test_pass_retry <- .tox/py27/local/lib/python2.7/site-packages/httpretty/core.py PASSED
tests/sbp/client/test_driver.py::test_tcp_logger PASSED
tests/sbp/client/test_handler.py::test_framer_receive_empty FAILED
tests/sbp/client/test_handler.py::test_framer_receive_bad_preamble PASSED
tests/sbp/client/test_handler.py::test_framer_bad_crc PASSED
tests/sbp/client/test_handler.py::test_framer_ok PASSED
tests/sbp/client/test_handler.py::test_handler_callbacks PASSED
tests/sbp/client/test_handler.py::test_multiple_handler_callbacks PASSED
tests/sbp/client/test_handler.py::test_child_iter PASSED
tests/sbp/client/test_handler.py::test_filter PASSED
tests/sbp/client/test_handler.py::test_dead_gc PASSED
tests/sbp/client/test_handler.py::test_late_iter PASSED
tests/sbp/client/test_logger.py::test_log PASSED
tests/sbp/client/test_logger.py::test_json_log PASSED
tests/sbp/client/test_logger.py::test_non_utf8_json_log PASSED
tests/sbp/client/test_logger.py::test_msg_print xfail
tests/sbp/client/test_logger.py::test_udp_logger PASSED
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 51053)
Traceback (most recent call last):
tests/sbp/client/test_logger.py::test_rolling_json_log   File "/usr/lib/python2.7/SocketServer.py", line 596, in process_request_thread
PASSED

=============================================================================================== FAILURES ================================================================================================
__________________________________________________________________________________________ test_http_test_pass __________________________________________________________________________________________

args = (), kw = {}

    @functools.wraps(test)
    def wrapper(*args, **kw):
        with httprettized():
>           return test(*args, **kw)

.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1627:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/sbp/client/test_driver.py:80: in test_http_test_pass
    content_type=b"application/vnd.swiftnav.broker.v1+sbp2")
.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1303: in register_uri
    cls.Response(method=method, uri=uri, **headers),
.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1346: in Response
    return Entry(method, uri, **kw)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'Entry' object has no attribute 'status'") raised in repr()] SafeRepr object at 0x7fdec7506758>, method = 'GET', uri = 'http://broker.testing.skylark.swiftnav.com/'
body = array([ 85,  16,   0,  66,   0,   4,  97,  98,  99, 100, 193, 207],
      dtype=uint8), adding_headers = None, forcing_headers = None, status = 200, streaming = False
headers = {'content_type': 'application/vnd.swiftnav.broker.v1+sbp2'}

    def __init__(self, method, uri, body,
                 adding_headers=None,
                 forcing_headers=None,
                 status=200,
                 streaming=False,
                 **headers):

        self.method = method
        self.uri = uri
        self.info = None
        self.request = None

        self.body_is_callable = False
        if hasattr(body, "__call__"):
            self.callable_body = body
            self.body = None
            self.body_is_callable = True
        elif isinstance(body, text_type):
            self.body = utf8(body)
        else:
            self.body = body

        self.streaming = streaming
        if not streaming and not self.body_is_callable:
>           self.body_length = len(self.body or '')
E           ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:673: ValueError
__________________________________________________________________________________________ test_http_test_fail __________________________________________________________________________________________

args = (), kw = {}

    @functools.wraps(test)
    def wrapper(*args, **kw):
        with httprettized():
>           return test(*args, **kw)

.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1627:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/sbp/client/test_driver.py:116: in test_http_test_fail
    status=400)
.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1303: in register_uri
    cls.Response(method=method, uri=uri, **headers),
.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1346: in Response
    return Entry(method, uri, **kw)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'Entry' object has no attribute 'status'") raised in repr()] SafeRepr object at 0x7fdec8414e18>, method = 'GET', uri = 'http://broker.testing.skylark.swiftnav.com/'
body = array([ 85,  16,   0,  66,   0,   4,  97,  98,  99, 100, 193, 207],
      dtype=uint8), adding_headers = None, forcing_headers = None, status = 400, streaming = False
headers = {'content_type': 'application/vnd.swiftnav.broker.v1+sbp2'}

    def __init__(self, method, uri, body,
                 adding_headers=None,
                 forcing_headers=None,
                 status=200,
                 streaming=False,
                 **headers):

        self.method = method
        self.uri = uri
        self.info = None
        self.request = None

        self.body_is_callable = False
        if hasattr(body, "__call__"):
            self.callable_body = body
            self.body = None
            self.body_is_callable = True
        elif isinstance(body, text_type):
            self.body = utf8(body)
        else:
            self.body = body

        self.streaming = streaming
        if not streaming and not self.body_is_callable:
>           self.body_length = len(self.body or '')
E           ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:673: ValueError
_____________________________________________________________________________________ test_http_test_pass_streaming _____________________________________________________________________________________

args = (), kw = {}

    @functools.wraps(test)
    def wrapper(*args, **kw):
        with httprettized():
>           return test(*args, **kw)

.tox/py27/local/lib/python2.7/site-packages/httpretty/core.py:1627:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    @activate
    def test_http_test_pass_streaming():
      assert is_enabled()
      msgs = [MsgPrintDep(text=b'foo'),
              MsgPrintDep(text=b'bar'),
              MsgPrintDep(text=b'baz')]
      register_uri(GET,
                   BASE_STATION_URI,
                   mock_streaming_msgs([m.to_binary() for m in msgs]),
                   content_type=b"application/vnd.swiftnav.broker.v1+sbp2",
                   streaming=True)
      register_uri(PUT,
                   BASE_STATION_URI,
                   body=b'',
                   content_type=b"application/vnd.swiftnav.broker.v1+sbp2",
                   streaming=True)
      with HTTPDriver(device_uid=b"Swift22", url=BASE_STATION_URI) as driver:
        assert driver.connect_read()
        assert driver.read_ok
>       assert driver.read(size=255) == b''.join([m.to_binary() for m in msgs])
E       TypeError: sequence item 0: expected string, numpy.ndarray found

tests/sbp/client/test_driver.py:152: TypeError
_______________________________________________________________________________________ test_framer_receive_empty _______________________________________________________________________________________

    def test_framer_receive_empty():
      source = io.BytesIO(b"")
      framer = Framer(source.read, None)
>     assert framer._receive() == None

tests/sbp/client/test_handler.py:37:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <sbp.client.framer.Framer object at 0x7fdec7523cd0>

    def _receive(self):
        """
            Read and build SBP message.
            """
        # preamble - not readall(1) to allow breaking before messages,
        # empty input
        preamble = self._read(1)
        if not preamble:
>           raise StopIteration
E           StopIteration

sbp/client/framer.py:118: StopIteration
=========================================================================================== warnings summary ============================================================================================
tests/sbp/client/test_logger.py::test_rolling_json_log
  /mnt/users/pasi/swiftnav/libsbp/python/tests/sbp/client/test_logger.py:89: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
    assert data == self.request[0].strip()

-- Docs: http://doc.pytest.org/en/latest/warnings.html
====================================================================== 4 failed, 27 passed, 1 xfailed, 1 warnings in 15.96 seconds ======================================================================
ERROR: InvocationError: '/mnt/users/pasi/swiftnav/libsbp/python/.tox/py27/bin/py.test -v tests/'
py35 create: /mnt/users/pasi/swiftnav/libsbp/python/.tox/py35
py35 installdeps: -r/mnt/users/pasi/swiftnav/libsbp/python/requirements.txt, -r/mnt/users/pasi/swiftnav/libsbp/python/test_requirements.txt
py35 inst: /mnt/users/pasi/swiftnav/libsbp/python/.tox/dist/sbp-2.5.0.dev18+g4680db9.zip
py35 installed: certifi==2019.3.9,cffi==1.12.2,chardet==3.0.4,construct==2.9.33,cov-core==1.15.0,coverage==4.4.1,filelock==3.0.10,futures==3.1.1,httpretty==0.9.4,idna==2.8,llvmlite==0.28.0,numba==0.43.0,numpy==1.16.2,pluggy==0.9.0,py==1.8.0,pybase64==0.5.0,pycparser==2.19,pyftdi==0.13.4,pylibftdi==0.17.0,pyserial==3.4,pytest==3.1.2,pytest-cov==2.5.1,pyusb==1.0.2,requests==2.21.0,requests-futures==0.9.9,ruamel.yaml==0.15.89,sbp==2.5.0.dev18+g4680db9,six==1.12.0,toml==0.10.0,tox==3.7.0,urllib3==1.24.1,virtualenv==16.4.3
py35 runtests: PYTHONHASHSEED='2715266942'
py35 runtests: commands[0] | py.test -v tests/
========================================================================================== test session starts ==========================================================================================
platform linux -- Python 3.5.2, pytest-3.1.2, py-1.8.0, pluggy-0.4.0 -- /mnt/users/pasi/swiftnav/libsbp/python/.tox/py35/bin/python3.5
cachedir: .cache
rootdir: /mnt/users/pasi/swiftnav/libsbp/python, inifile: pytest.ini
plugins: cov-2.5.1
collected 25 items / 2 errors

================================================================================================ ERRORS =================================================================================================
_______________________________________________________________________________ ERROR collecting tests/sbp/test_numba.py ________________________________________________________________________________
tests/sbp/test_numba.py:5: in <module>
    from sbp.jit.file_io import MsgFileioWriteReq as MsgFileioWriteReq_j
sbp/jit/file_io.py:27: in <module>
    from sbp.jit.msg import SBP, SENDER_ID
sbp/jit/msg.py:23: in <module>
    from sbp.jit import parse_float_c
E   ImportError: dynamic module does not define module export function (PyInit_parse_float_c)
_______________________________________________________________________________ ERROR collecting tests/sbp/test_numba.py ________________________________________________________________________________
ImportError while importing test module '/mnt/users/pasi/swiftnav/libsbp/python/tests/sbp/test_numba.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/sbp/test_numba.py:5: in <module>
    from sbp.jit.file_io import MsgFileioWriteReq as MsgFileioWriteReq_j
sbp/jit/file_io.py:27: in <module>
    from sbp.jit.msg import SBP, SENDER_ID
sbp/jit/msg.py:23: in <module>
    from sbp.jit import parse_float_c
E   ImportError: dynamic module does not define module export function (PyInit_parse_float_c)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
======================================================================================== 2 error in 3.62 seconds ========================================================================================
ERROR: InvocationError: '/mnt/users/pasi/swiftnav/libsbp/python/.tox/py35/bin/py.test -v tests/'
py37 create: /mnt/users/pasi/swiftnav/libsbp/python/.tox/py37
ERROR: InterpreterNotFound: python3.7
________________________________________________________________________________________________ summary ________________________________________________________________________________________________
ERROR:   py27: commands failed
ERROR:   py35: commands failed
SKIPPED:  py37: InterpreterNotFound: python3.7
Makefile:214: recipe for target 'test-python' failed
make: *** [test-python] Error 1
pasi@pasi-linux:~/swiftnav/libsbp$ ls -la python/sbp/jit/parse_float*
-rw-r--r-- 1 pasi users 24368 Mar 25 09:15 python/sbp/jit/parse_float_c.c
-rw-r--r-- 1 pasi users 36256 Mar 25 09:16 python/sbp/jit/parse_float_c.o
-rwxr-xr-x 1 pasi users 30336 Mar 25 09:16 python/sbp/jit/parse_float_c.so
-rw-r--r-- 1 pasi users   975 Mar 25 09:30 python/sbp/jit/parse_float.py

@pmiettinen
Copy link
Contributor

Changes for Python 2.7. Not sure how legit these are.

pasi@pasi-linux:~/swiftnav/libsbp$ git diff
diff --git a/python/sbp/client/framer.py b/python/sbp/client/framer.py
index d846b97..ab148d0 100644
--- a/python/sbp/client/framer.py
+++ b/python/sbp/client/framer.py
@@ -115,7 +115,7 @@ class Framer(six.Iterator):
         # empty input
         preamble = self._read(1)
         if not preamble:
-            raise StopIteration
+            return None
         elif ord(preamble) != SBP_PREAMBLE:
             if self._verbose:
                 print("Host Side Unhandled byte: 0x%02x" % ord(preamble))
diff --git a/python/sbp/tracking.py b/python/sbp/tracking.py
index c11bacf..56e0dbf 100644
--- a/python/sbp/tracking.py
+++ b/python/sbp/tracking.py
@@ -1126,6 +1126,26 @@ update interval.
     self.payload = MsgTrackingIqDepB._parser.build(c)
     return self.pack()

+  def into_buffer(self, buf, offset):
+    """Produce a framed/packed SBP message into the provided buffer and offset.
+
+    """
+    self.payload = containerize(exclude_fields(self))
+    def build_payload(buf, offset, payload):
+        total_length = [0]
+        class StreamPayload(object):
+            def write(self, data):
+                try:
+                    length = len(data)
+                    buf[offset:offset+length] = bytearray(data)
+                    total_length[0] += length
+                    return length
+                except Exception as exc:
+                    print(exc)
+        MsgTrackingIqDepB._parser.build_stream(payload, StreamPayload())
+        return total_length[0]
+    return self.pack_into(buf, offset, build_payload)
+
   def to_json_dict(self):
     self.to_binary()
     d = super( MsgTrackingIqDepB, self).to_json_dict()
@@ -1232,7 +1252,7 @@ class MsgTrackingIqDepA(SBP):
                     return length
                 except Exception as exc:
                     print(exc)
-        MsgTrackingIqDep._parser.build_stream(payload, StreamPayload())
+        MsgTrackingIqDepA._parser.build_stream(payload, StreamPayload())
         return total_length[0]
     return self.pack_into(buf, offset, build_payload)

diff --git a/python/tests/sbp/client/test_driver.py b/python/tests/sbp/client/test_driver.py
index bd43959..f3334b5 100755
--- a/python/tests/sbp/client/test_driver.py
+++ b/python/tests/sbp/client/test_driver.py
@@ -38,7 +38,7 @@ def tcp_server(handler):
   return (ip, port)

 def test_tcp_logger():
-  handler = tcp_handler(MsgPrintDep(text=b'abc').to_binary())
+  handler = tcp_handler(MsgPrintDep(text=b'abc').to_binary().tostring())
   ip, port = tcp_server(handler)
   port = "socket://%s:%s" % (ip, port)
   baud = 115200
@@ -76,7 +76,7 @@ def test_http_test_pass():
   msg = MsgPrintDep(text=b'abcd')
   register_uri(GET,
                BASE_STATION_URI,
-               msg.to_binary(),
+               msg.to_binary().tostring(),
                content_type=b"application/vnd.swiftnav.broker.v1+sbp2")
   register_uri(PUT,
                BASE_STATION_URI,
@@ -86,7 +86,7 @@ def test_http_test_pass():
     assert not driver.read_ok
     assert driver.connect_read()
     assert driver.read_ok
-    assert driver.read(size=255) == msg.to_binary()
+    assert driver.read(size=255) == msg.to_binary().tostring()
     with pytest.raises(IOError):
       assert driver.read(size=255)
     assert not driver.read_close()
@@ -111,7 +111,7 @@ def test_http_test_fail():
   msg = MsgPrintDep(text=b'abcd')
   register_uri(GET,
                BASE_STATION_URI,
-               msg.to_binary(),
+               msg.to_binary().tolist(),
                content_type=b"application/vnd.swiftnav.broker.v1+sbp2",
                status=400)
   register_uri(PUT,
@@ -138,7 +138,7 @@ def test_http_test_pass_streaming():
           MsgPrintDep(text=b'baz')]
   register_uri(GET,
                BASE_STATION_URI,
-               mock_streaming_msgs([m.to_binary() for m in msgs]),
+               mock_streaming_msgs([m.to_binary().tostring() for m in msgs]),
                content_type=b"application/vnd.swiftnav.broker.v1+sbp2",
                streaming=True)
   register_uri(PUT,
@@ -149,7 +149,7 @@ def test_http_test_pass_streaming():
   with HTTPDriver(device_uid=b"Swift22", url=BASE_STATION_URI) as driver:
     assert driver.connect_read()
     assert driver.read_ok
-    assert driver.read(size=255) == b''.join([m.to_binary() for m in msgs])
+    assert driver.read(size=255) == b''.join([m.to_binary().tostring() for m in msgs])
     assert driver.read(size=255) == b''
     assert not driver.read_close()
     assert driver.read_response is None
pasi@pasi-linux:~/swiftnav/libsbp$

After this Python 3.5 still failing

========================================================================================== test session starts ==========================================================================================
platform linux -- Python 3.5.0, pytest-3.1.2, py-1.8.0, pluggy-0.4.0 -- /mnt/users/pasi/swiftnav/libsbp/python/.tox/py35/bin/python
cachedir: .cache
rootdir: /mnt/users/pasi/swiftnav/libsbp/python, inifile: pytest.ini
plugins: cov-2.5.1
collected 32 items

tests/sbp/test_messages.py::test_messages PASSED
tests/sbp/test_numba.py::test_get_string FAILED
tests/sbp/test_numba.py::test_get_string_no_null FAILED
tests/sbp/test_numba.py::test_get_string_offset_no_null FAILED
tests/sbp/test_numba.py::test_get_string_offset FAILED
tests/sbp/test_numba.py::test_get_fixed_string_offset FAILED
tests/sbp/test_numba.py::test_parse PASSED
tests/sbp/test_numba.py::test_jit PASSED
tests/sbp/test_table.py::test_table_count PASSED
tests/sbp/test_table.py::test_table_unqiue_count PASSED
tests/sbp/test_table.py::test_available_messages PASSED
tests/sbp/client/test_driver.py::test_tcp_logger PASSED
tests/sbp/client/test_driver.py::test_http_test_pass PASSED
tests/sbp/client/test_driver.py::test_http_test_fail PASSED
tests/sbp/client/test_driver.py::test_http_test_pass_streaming PASSED
tests/sbp/client/test_driver.py::test_http_test_pass_retry PASSED
tests/sbp/client/test_handler.py::test_framer_receive_empty PASSED
tests/sbp/client/test_handler.py::test_framer_receive_bad_preamble PASSED
tests/sbp/client/test_handler.py::test_framer_bad_crc PASSED
tests/sbp/client/test_handler.py::test_framer_ok PASSED
tests/sbp/client/test_handler.py::test_handler_callbacks PASSED
tests/sbp/client/test_handler.py::test_multiple_handler_callbacks PASSED
tests/sbp/client/test_handler.py::test_child_iter PASSED
tests/sbp/client/test_handler.py::test_filter PASSED
tests/sbp/client/test_handler.py::test_dead_gc PASSED
tests/sbp/client/test_handler.py::test_late_iter PASSED
tests/sbp/client/test_logger.py::test_log PASSED
tests/sbp/client/test_logger.py::test_json_log PASSED
tests/sbp/client/test_logger.py::test_non_utf8_json_log PASSED
tests/sbp/client/test_logger.py::test_msg_print xfail
tests/sbp/client/test_logger.py::test_udp_logger PASSED
tests/sbp/client/test_logger.py::test_rolling_json_log PASSED

=============================================================================================== FAILURES ================================================================================================
____________________________________________________________________________________________ test_get_string ____________________________________________________________________________________________

    def test_get_string():
>       s = _mk_string('thisisastring')

tests/sbp/test_numba.py:21:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

val = 'thisisastring', null = '\x00'

    def _mk_string(val, null='\x00'):
>       ba = bytearray(val + (null if null is not None else ''))
E       TypeError: string argument without an encoding

tests/sbp/test_numba.py:16: TypeError
________________________________________________________________________________________ test_get_string_no_null ________________________________________________________________________________________

    def test_get_string_no_null():
>       s = _mk_string('thisisastring', null=None)

tests/sbp/test_numba.py:28:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

val = 'thisisastring', null = None

    def _mk_string(val, null='\x00'):
>       ba = bytearray(val + (null if null is not None else ''))
E       TypeError: string argument without an encoding

tests/sbp/test_numba.py:16: TypeError
____________________________________________________________________________________ test_get_string_offset_no_null _____________________________________________________________________________________

    def test_get_string_offset_no_null():
>       s = _mk_string('________thisisastring', null=None)

tests/sbp/test_numba.py:35:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

val = '________thisisastring', null = None

    def _mk_string(val, null='\x00'):
>       ba = bytearray(val + (null if null is not None else ''))
E       TypeError: string argument without an encoding

tests/sbp/test_numba.py:16: TypeError
________________________________________________________________________________________ test_get_string_offset _________________________________________________________________________________________

    def test_get_string_offset():
>       s = _mk_string('________thisisastring')

tests/sbp/test_numba.py:42:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

val = '________thisisastring', null = '\x00'

    def _mk_string(val, null='\x00'):
>       ba = bytearray(val + (null if null is not None else ''))
E       TypeError: string argument without an encoding

tests/sbp/test_numba.py:16: TypeError
_____________________________________________________________________________________ test_get_fixed_string_offset ______________________________________________________________________________________

    def test_get_fixed_string_offset():
>       s = _mk_string('________thisisastring')

tests/sbp/test_numba.py:49:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

val = '________thisisastring', null = '\x00'

    def _mk_string(val, null='\x00'):
>       ba = bytearray(val + (null if null is not None else ''))
E       TypeError: string argument without an encoding

tests/sbp/test_numba.py:16: TypeError
=========================================================================================== warnings summary ============================================================================================
tests/sbp/client/test_logger.py::test_rolling_json_log
  /mnt/users/pasi/swiftnav/libsbp/python/tests/sbp/client/test_logger.py:89: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
    assert data == self.request[0].strip()

-- Docs: http://doc.pytest.org/en/latest/warnings.html
====================================================================== 5 failed, 26 passed, 1 xfailed, 1 warnings in 13.83 seconds ======================================================================

@pmiettinen
Copy link
Contributor

Changes for py3

pasi@pasi-linux:~/swiftnav/libsbp$ git diff python/tests/sbp/test_numba.py
diff --git a/python/tests/sbp/test_numba.py b/python/tests/sbp/test_numba.py
index fc5dbaf..bf4cfcc 100644
--- a/python/tests/sbp/test_numba.py
+++ b/python/tests/sbp/test_numba.py
@@ -13,7 +13,7 @@ from sbp.jit.table import dispatch


 def _mk_string(val, null='\x00'):
-    ba = bytearray(val + (null if null is not None else ''))
+    ba = bytearray(val + (null if null is not None else ''), 'ascii')
     return np.array(ba, dtype=np.uint8)


@@ -21,35 +21,35 @@ def test_get_string():
     s = _mk_string('thisisastring')
     out, offset, length = get_string(s, 0, len(s))
     assert len(out) == len('thisisastring')
-    assert out == 'thisisastring'
+    assert out == b'thisisastring'


 def test_get_string_no_null():
     s = _mk_string('thisisastring', null=None)
     out, offset, length = get_string(s, 0, len(s))
     assert len(out) == len('thisisastring')
-    assert out == 'thisisastring'
+    assert out == b'thisisastring'


 def test_get_string_offset_no_null():
     s = _mk_string('________thisisastring', null=None)
     out, offset, length = get_string(s, 8, len(s) - 8)
     assert len(out) == len('thisisastring')
-    assert out == 'thisisastring'
+    assert out == b'thisisastring'


 def test_get_string_offset():
     s = _mk_string('________thisisastring')
     out, offset, length = get_string(s, 8, len(s))
     assert len(out) == len('thisisastring')
-    assert out == 'thisisastring'
+    assert out == b'thisisastring'


pasi@pasi-linux:~/swiftnav/libsbp$

@pmiettinen
Copy link
Contributor

Floating point parsing produces different results than the original serial_link.py approach.

I have a log from one TTFF run and fix reported in the json file from

sbp2json.py
"lat":61.44696044921875,"lon":23.858476638793945

serial_link.py
"lat":61.44696172596402,"lon":23.8584758593452

Latter matches with Haskell sbp2json. Random online calculator gives 15 cm distance between these points.

@pmiettinen
Copy link
Contributor

Solution for above: get_f64 in parse_float.py needs to return double, not float.

@pmiettinen
Copy link
Contributor

String handling doesn't cope with the settings NULL delimiter
Got:
{"crc":9354,"length":43,"msg_type":175,"payload":"AHVhcnQxAGZsb3dfY29udHJvbABOb25lAGVudW06Tm9uZSxSVFMvQ1RTAA==\n","setting":"uart1","status":0}
Expected:
{"crc":9354,"length":43,"msg_type":175,"payload":"AHVhcnQxAGZsb3dfY29udHJvbABOb25lAGVudW06Tm9uZSxSVFMvQ1RTAA==","preamble":85,"sender":45490,"setting":"uart1\u0000flow_control\u0000None\u0000enum:None,RTS/CTS\u0000","status":0}

@pmiettinen
Copy link
Contributor

parse_members should store nb.float32 and nb.float64 instead of Python builtin float. See this JIRA issue comment for details.

@pmiettinen
Copy link
Contributor

Possible solution for the settings NULL delimiter issue:

@nb.jit('Tuple((u1[:],u4,u4))(u1[:],u4,u4)', nopython=True, nogil=True)
def _get_string(buf_in, offset, length):
    buf_out = np.zeros(256, dtype=np.uint8)
    i = nb.u4(0)
    while i < length:
        buf_out[i] = buf_in[offset + i]
        i = nb.u4(i + nb.u4(1))

    return buf_out[:i], offset + i, i

@pmiettinen
Copy link
Contributor

At the end of every payload there's extra \n for some reason.

Example:
"payload":"AHVhcnQxAGZsb3dfY29udHJvbABOb25lAGVudW06Tm9uZSxSVFMvQ1RTAA==\n"

@pmiettinen
Copy link
Contributor

Possible solution for the settings NULL delimiter issue:

@nb.jit('Tuple((u1[:],u4,u4))(u1[:],u4,u4)', nopython=True, nogil=True)
def _get_string(buf_in, offset, length):
    buf_out = np.zeros(256, dtype=np.uint8)
    i = nb.u4(0)
    while i < length:
        buf_out[i] = buf_in[offset + i]
        i = nb.u4(i + nb.u4(1))

    return buf_out[:i], offset + i, i

This doesn't work with MsgFileioWriteReq.

@pmiettinen
Copy link
Contributor

Something like this instead:

@nb.jit('Tuple((u1[:],u4,u4))(u1[:],u4,u4)', nopython=True, nogil=True)
def _get_setting(buf_in, offset, length):
    buf_out = np.zeros(256, dtype=np.uint8)
    i = nb.u4(0)
    while i < length:
        buf_out[i] = buf_in[offset + i]
        i = nb.u4(i + nb.u4(1))

    return buf_out[:i], offset + i, i

def get_setting(buf, offset, length):
    buf, offset, length = _get_setting(buf, offset, length)
    return buf.tobytes(), offset, length
    ((*- if f.identifier == 'setting' *))
    o_1, __setting_type, (__setting, offset, length) = offset, 'None', get_setting(buf, offset, length)
    ((* else *))
    o_1, __(((f.identifier)))_type, (__(((f.identifier))), offset, length) = offset, '((( f.type_id | numba_float_type )))', ((( f | numba_py )))(buf, offset, length)
    ((* endif *))

@pmiettinen
Copy link
Contributor

Regarding the newline on the payload value.

https://buildmedia.readthedocs.org/media/pdf/pybase64/latest/pybase64.pdf

2.3 Legacy API Reference
pybase64.encodebytes(s)
Encode bytes into a bytes object with newlines (b’ ‘) inserted after every 76 bytes of output, and ensuring that there is a trailing newline, as per RFC 2045 (MIME).
Argument s is a bytes-like object to encode.
The result is returned as a bytes object.

To mitigate it and convert to str object at the same time:
res['payload'] = pybase64.encodebytes(self.payload.tobytes()).decode('ascii').strip('\n')

@pmiettinen
Copy link
Contributor

Some failure happening and empty JSON object is printed in case of few messages

sbp2json.py

{"crc":4949,"flags":2147615488,"length":4,"msg_type":65535,"payload":"AAMCgA=="}
{}
{}
{"cpu":1,"crc":3664,"length":26,"msg_type":23,"name":"rpmsg","payload":"cnBtc2cAAAAAAAAAAAAAAAAAAAABAPwNAAA=","stack_free":3580}
{}
{}
{"cpu":0,"crc":44965,"length":26,"msg_type":23,"name":"manage PV","payload":"bWFuYWdlIFBWAAAAAAAAAAAAAAAAAMwEAAA=","stack_free":1228}
{}
{"cpu":0,"crc":19574,"length":26,"msg_type":23,"name":"IMU aux","payload":"SU1VIGF1eAAAAAAAAAAAAAAAAAAAAAQJAAA=","stack_free":2308}
{}

Expected

{"crc":4949,"flags":2147615488,"length":4,"msg_type":65535,"payload":"AAMCgA==","preamble":85,"sender":45490}
{"cpu":3,"crc":60639,"length":26,"msg_type":23,"name":"main\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"bWFpbgAAAAAAAAAAAAAAAAAAAAADALxzAAA=","preamble":85,"sender":45490,"stack_free":29628}
{"cpu":955,"crc":18613,"length":26,"msg_type":23,"name":"idle\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"aWRsZQAAAAAAAAAAAAAAAAAAAAC7A3wAAAA=","preamble":85,"sender":45490,"stack_free":124}
{"cpu":1,"crc":3664,"length":26,"msg_type":23,"name":"rpmsg\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"cnBtc2cAAAAAAAAAAAAAAAAAAAABAPwNAAA=","preamble":85,"sender":45490,"stack_free":3580}
{"cpu":1,"crc":21969,"length":26,"msg_type":23,"name":"SBP\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"U0JQAAAAAAAAAAAAAAAAAAAAAAABAPz/AAA=","preamble":85,"sender":45490,"stack_free":65532}
{"cpu":0,"crc":60142,"length":26,"msg_type":23,"name":"NAP\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"TkFQAAAAAAAAAAAAAAAAAAAAAAAAAMSAAAA=","preamble":85,"sender":45490,"stack_free":32964}
{"cpu":0,"crc":44965,"length":26,"msg_type":23,"name":"manage PV\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"bWFuYWdlIFBWAAAAAAAAAAAAAAAAAMwEAAA=","preamble":85,"sender":45490,"stack_free":1228}
{"cpu":0,"crc":26710,"length":26,"msg_type":23,"name":"IMU\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"SU1VAAAAAAAAAAAAAAAAAAAAAAAAAPwHAAA=","preamble":85,"sender":45490,"stack_free":2044}
{"cpu":0,"crc":19574,"length":26,"msg_type":23,"name":"IMU aux\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"SU1VIGF1eAAAAAAAAAAAAAAAAAAAAAQJAAA=","preamble":85,"sender":45490,"stack_free":2308}
{"cpu":0,"crc":59610,"length":26,"msg_type":23,"name":"ndb\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000","payload":"bmRiAAAAAAAAAAAAAAAAAAAAAAAAAGwNAAA=","preamble":85,"sender":45490,"stack_free":3436}

Note also preamble and sender elements missing.

@silverjam silverjam force-pushed the silverjam/numba-jit2 branch from 8efa20d to bbae725 Compare April 3, 2019 22:56
@pmiettinen pmiettinen changed the base branch from silverjam/numba-jit to master April 4, 2019 07:49
@pmiettinen pmiettinen force-pushed the silverjam/numba-jit2 branch 8 times, most recently from da9e562 to 601e04e Compare April 4, 2019 12:43
@pmiettinen
Copy link
Contributor

In case of CRC failure, few following messages are lost compared to non-jit sbp2json.

@pmiettinen pmiettinen force-pushed the silverjam/numba-jit2 branch from 76b960d to 1112801 Compare April 5, 2019 09:37
@pmiettinen pmiettinen force-pushed the silverjam/numba-jit2 branch from 3666963 to 60ee7f4 Compare April 17, 2019 08:21
@silverjam silverjam changed the title [WIP] Python: Numba based SBP parsing Python: Numba based SBP parsing [ESD-1154] Apr 18, 2019
Copy link
Contributor Author

@silverjam silverjam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this superseded by #667? Should we close this?

}
"""

module_name = "parse_float_c"
Copy link
Contributor Author

@silverjam silverjam Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Echoing comments on #667, it looks like we need to pick one of the options from here: https://cffi.readthedocs.io/en/latest/cdef.html and implement it so that we don't have to manually invoke this script prior to using sbp.jit.

while len(data) < size:
d = self._read(size - len(data))
if self._broken:
if not d or self._broken:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an optional behavior? Do we know this will affect the console and it's command line utilities?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs investigation.

@pmiettinen
Copy link
Contributor

#667 currently contains exactly the same commits as this PR so in that sense this can be closed.

@silverjam
Copy link
Contributor Author

Closing in favor of #667

@silverjam silverjam closed this May 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants