commit - 63f886b17113fc454e9bee0d414ad13f433b1989
commit + b335c8c7de73dfe3caeb88f91f8b3bc895c19497
blob - 95bd134181e8918044f066de53b7d3ddf9670b13
blob + c7d69705cf53531a05d97d641404660df1c3016e
--- README.md
+++ README.md
# mbed-memtrace-logger
+
Analyzes and logs the memtrace output from mbed-os in a readable form.
+This code is left over for the use with micro:bit and Calliope mini.
-The memory trace outputs pretty unreadable information:
+If you look for the original memtrace, check out the mbed-cli.
-```
-#m:0x20003080;0x182f-50
-#f:0x0;0x183f-0x20003080
-#m:0x20003080;0x182f-50
-#f:0x0;0x183f-0x20003080
-#m:0x20003080;0x182f-50
-#f:0x0;0x183f-0x20003080
-```
+> You need to enable heap debug output!
-The script takes those lines and converts them into a little more readable
-information and also calculates the currently allocated heap:
+Run with: `python3 memtrace.py /dev/cu.XXXXX 115200`
+The script auto-resets the counts and output looks like:
```
-$ python bin/memtrace.py test.log test
-RESETTING tracer on 'test'
-
-== (001) 50 [ 32] +50 (#m:0x20003080;0x182f-50)
-== (000) 0 [ 0] -50 (#f:0x0;0x183f-0x20003080)
-== (001) 50 [ 32] +50 (#m:0x20003080;0x182f-50)
-== (000) 0 [ 0] -50 (#f:0x0;0x183f-0x20003080)
-== (001) 50 [ 32] +50 (#m:0x20003080;0x182f-50)
-== (000) 0 [ 0] -50 (#f:0x0;0x183f-0x20003080)
+HEAP 0:
+heap_start : 0x20002950
+heap_end : 0x20003600
+heap_size : 3248
+[F:3248]
+mb_total_free : 3248
+mb_total_used : 0
+== (001) 56 [20002954] +56 (malloc: ALLOCATED: 56 [0x20002954])
+== (002) 76 [20002990] +20 (malloc: ALLOCATED: 20 [0x20002990])
+== (003) 124 [200029a8] +48 (malloc: ALLOCATED: 48 [0x200029a8])
+...
+== (032) 1254 [20002e50] -18 (free: 0x20002e50)
+== (031) 1229 [20002ea8] -25 (free: 0x20002ea8)
+== (030) 1209 [20002e90] -20 (free: 0x20002e90)
+...
+Calliope Accelerometer Test v1.0
```
-Even in color :-):
-
-
-
-# Using a log file
-- enable memory tracing in your mbed program
-- log uart output into a file
-- run script to analyze memory state
-
-# Using a stream
-- enable memory tracing in your mbed program
-- log uart into a log file continuously: `miniterm.py /dev/cu.usbmodem1234 9600 | tee memtrace.log`
-- run script to tail file `python memtrace.py memtrace.log KEYWORD`
-
-> the KEYWORD allows you to define a RESET state, where the analyzer resets all calculations.
-
-# Required libraries:
-
-- `Pygtail`, install: `pip install pygtail`
-
-Enjoy!
-
## License
```
blob - 232f8f4134a6d2b32c404995632a116bd405e28c (mode 644)
blob + /dev/null
--- memtrace-calliope.py
+++ /dev/null
-"""
-A simple mbed memtrace log extractor, calculating allocated memory.
-
-Author: Matthias L. Jugel (@thinkberg)
-
-Prerequisites:
- pip install pygtail
-
-Usage:
- python memtrace.py log-file mem-reset-keyword
-
- The script will tail the log file and reset the memory counter when
- the mem-reset-keyword occurs.
-
- a) enable memory tracing in your embedded program (config.json):
- ```
- {
- "microbit-dal": {
- "debug": 1,
- "heap": {
- "debug": 1
- }
- }
- }
- ```
-
- b) Log into a file:
- `$ miniterm.py /dev/cu.usbmodem142111 9600 | tee memtrace.log`
-
- c) Analyze log file (tails the log file)
- python -u bin/memtrace.py memtrace.log SHCSR
-
- If the analyzer behaves strangely, delete the "log-file.offset" file.
-"""
-
-import re
-import sys
-
-import time
-
-from pygtail import Pygtail
-
-mem = {}
-allocated = 0
-
-reset = None
-if len(sys.argv) > 1:
- reset = sys.argv[2]
- print "RESETTING tracer on '%s'" % reset
-
-r_malloc = re.compile("^(microbit_)malloc:\\s+(NATIVE\\s+)?(ALLOCATED:)\\s+(\\d+)\\s+\\[(0x[0-9a-f]+)\\]")
-r_free = re.compile("^(microbit_)free:\\s+(0x[0-9a-f]+)")
-
-partial = ""
-while True:
- for line in Pygtail(sys.argv[1]):
- # we sometimes get incomplete lines, wait for a full line
- if not (line[-1] == '\n' or line[-1] == '\r'):
- partial = line
- continue
- else:
- line = partial + line
- partial = ""
-
- # strip newline and carriage return
- line = line.rstrip('\n').rstrip('\r')
-
- # if we detect the reset keyword, rest the map and memory counter
- if reset in line:
- mem = {}
- allocated = 0
- print "\n\n\033[91m>> RESET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\033[0m"
-
- # match malloc, realloc and free
- m = r_malloc.search(line)
- if m:
- mem[m.group(5)] = int(m.group(4))
- allocated += int(m.group(4))
- print "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[31m+%-6d\033[0m (%s)" % \
- (len(mem), allocated, allocated, int(m.group(4)),
- m.group(0).replace(m.group(1), "").replace(m.group(3), ""))
- continue
-
- m = r_free.search(line)
- if m:
- # print "f", m.group(3)
- freed = 0
- if mem.has_key(m.group(2)):
- freed = mem[m.group(2)]
- allocated -= freed
- del mem[m.group(2)]
- else:
- print "\033[33m!! WARN: free(%s)\033[0m" % m.group(1)
- print "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[92m-%-6d\033[0m (%s)" % \
- (len(mem), allocated, allocated, freed, m.group(0).replace(m.group(1), ""))
- continue
-
- # print all other lines as is, so we can still use the log functionality
- print line
- sys.stdout.flush()
- time.sleep(1)
blob - 26db34e700d231ba149f3dea02860620fdd357db
blob + 923e2284830d1e0a67f22a19c83b24935c8803ef
--- memtrace.py
+++ memtrace.py
-"""
-A simple mbed memtrace log extractor, calculating allocated memory.
-
-Author: Matthias L. Jugel (@thinkberg)
-
-Prerequisites:
- pip install pygtail
-
-Usage:
- python memtrace.py log-file mem-reset-keyword
-
- The script will tail the log file and reset the memory counter when
- the mem-reset-keyword occurs.
-
- a) enable memory tracing in your embedded program:
- ```
- #ifdef MBED_MEM_TRACING_ENABLED
- mbed_mem_trace_set_callback(mbed_mem_trace_default_callback);
- #endif
- ```
-
- b) Log into a file:
- `$ miniterm.py /dev/cu.usbmodem142111 9600 | tee memtrace.log`
-
- c) Analyze log file (tails the log file)
- python -u bin/memtrace.py memtrace.log SHCSR
-
- If the analyzer behaves strangely, delete the "log-file.offset" file.
-"""
-
-import re
import sys
-import time
+from memtrace_calliope_trns import CalliopeDebugTransform
-from pygtail import Pygtail
-mem = {}
-allocated = 0
+class TraceTerminal(object):
+ serial = None # Serial() object
+ port = None
+ baudrate = None
+ echo = None
-reset = None
-if len(sys.argv) > 1:
- reset = sys.argv[2]
- print "RESETTING tracer on '%s'" % reset
+ def __init__(self, port, baudrate=9600, echo=True, timeout=10):
+ self.port = port
+ self.baudrate = int(baudrate)
+ self.timeout = int(timeout)
+ self.echo = False if str(echo).lower() == 'off' else True
-r_malloc = re.compile("([^#]*)#m:(0x[0-9a-f]+);(0x[0-9a-f]+)-(\\d+)")
-r_calloc = re.compile("([^#]*)#c:(0x[0-9a-f]+);(0x[0-9a-f]+)-(\\d+);(\\d+)")
-r_realloc = re.compile("([^#]*)#r:(0x[0-9a-f]+);(0x[0-9a-f]+)-(0x[0-9a-f]+);(\\d+)")
-r_free = re.compile("([^#]*)#f:(0x[0-9a-f]+);(0x[0-9a-f]+)-(0x[0-9a-f]+)")
+ try:
+ from serial import Serial, SerialException
+ self.serial = Serial(self.port, baudrate=self.baudrate, timeout=self.timeout)
+ self.serial.flush()
+ self.serial.reset_input_buffer()
+ except (IOError, ImportError, OSError, Exception):
+ self.serial = None
+ return
-partial = ""
-while True:
- for line in Pygtail(sys.argv[1]):
- # we sometimes get incomplete lines, wait for a full line
- if not (line[-1] == '\n' or line[-1] == '\r'):
- partial = line
- continue
- else:
- line = partial + line
- partial = ""
+ def terminal(self):
+ try:
+ import serial.tools.miniterm as miniterm
+ except (IOError, ImportError, OSError) as e:
+ print(repr(e))
+ return False
- # strip newline and carriage return
- line = line.rstrip('\n').rstrip('\r')
+ term = miniterm.Miniterm(self.serial, echo=self.echo)
+ term.exit_character = '\x03'
+ term.menu_character = '\x14'
+ term.set_rx_encoding('UTF-8')
+ term.set_tx_encoding('UTF-8')
- # if we detect the reset keyword, rest the map and memory counter
- if reset in line:
- mem = {}
- allocated = 0
- print "\n\n\033[91m>> RESET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\033[0m"
+ term.rx_transformations += [CalliopeDebugTransform()]
- # match malloc, realloc and free
- m = r_malloc.search(line)
- if m:
- if len(m.group(1)): print m.group(1)
- # print "m", m.group(2), ":", m.group(4)
- if m.group(2) == "0x0":
- print "\033[1m!! (%03d) \033[31mmalloc failed\033[0m (%s)" % (len(mem), line)
- else:
- mem[m.group(2)] = int(m.group(4))
- allocated += int(m.group(4))
- print "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[31m+%-6d\033[0m (%s)" % \
- (len(mem), allocated, allocated, int(m.group(4)), line.replace(m.group(1), ""))
- continue
+ term.start()
- m = r_calloc.search(line)
- if m:
- if len(m.group(1)): print m.group(1)
- # print "c", m.group(2), ":", m.group(4), "*", m.group(5)
- if m.group(2) == "0x0":
- print "\033[1m!! (%03d) \033[31mcalloc failed\033[0m (%s)" % (len(mem), line)
- else:
- total = int(m.group(4)) * int(m.group(5))
- mem[m.group(2)] = total
- allocated += total
- print "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[31m+%-6d\033[0m (%s)" % \
- (len(mem), allocated, allocated, total, line.replace(m.group(1), ""))
- continue
+ try:
+ term.join(True)
+ except KeyboardInterrupt:
+ pass
+ term.join()
+ term.close()
- m = r_realloc.search(line)
- if m:
- if len(m.group(1)): print m.group(1)
- # print "r", m.group(2), ":", m.group(5)
- diff = 0
- if mem.has_key(m.group(2)):
- diff = int(m.group(5)) - mem[m.group(2)]
- mem[m.group(2)] = int(m.group(5))
- else:
- print "\033[33m!! WARN: realloc() without previous allocation\033[0m (%s)" % line
- allocated += diff
- print "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[35m+%-6d\033[0m (%s)" % \
- (len(mem), allocated, allocated, diff, line.replace(m.group(1), ""))
- continue
+term = TraceTerminal(sys.argv[1], int(sys.argv[2])).terminal()
- m = r_free.search(line)
- if m:
- if len(m.group(1)): print m.group(1)
- # print "f", m.group(3)
- freed = 0
- if mem.has_key(m.group(4)):
- freed = mem[m.group(4)]
- allocated -= freed
- del mem[m.group(4)]
- else:
- print "\033[33m!! WARN: free(%s)\033[0m" % m.group(4)
- print "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[92m-%-6d\033[0m (%s)" % \
- (len(mem), allocated, allocated, freed, line.replace(m.group(1), ""))
- continue
- # print all other lines as is, so we can still use the log functionality
- print line
- sys.stdout.flush()
- time.sleep(1)
blob - /dev/null
blob + 4f35c1a0516289063bdbcaedce3d6a2f12a91bbb (mode 644)
--- /dev/null
+++ memtrace_calliope_trns.py
+# mbed debug trace transformer, handling memory trace information
+# Author: Matthias L. Jugel (@thinkberg)
+#
+# Copyright (c) 2018 ubirch GmbH, All Rights Reserved
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+#
+# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied.
+
+import re
+import sys
+
+from serial.tools.miniterm import Transform
+
+def debug(s):
+ sys.stderr.write("D "+repr(s)+"\r\n")
+
+class CalliopeDebugTransform(Transform):
+ mem = {}
+ allocated = 0
+ reset = None
+ buffer = ""
+
+ def __init__(self):
+ self.r_malloc = re.compile("(?:microbit_)?malloc:\s+(?:NATIVE\s+)?(?:ALLOCATED:)\s+(\d+)\s+\[(0x[0-9a-f]+)\]")
+ self.r_free = re.compile("^(?:microbit_)?free:\\s+(0x[0-9a-f]+)")
+ self.r_heap_size = re.compile("^mb_total_free : (\d+)")
+
+ def rx(self, rx_input):
+ # collect lines
+ out = ""
+ for c in rx_input:
+ if c == '\r' or c == '\n':
+ if len(self.buffer):
+ out += self.trace_line(self.buffer)
+ else:
+ out += "\n"
+ self.buffer = ""
+ continue
+ else:
+ self.buffer += c
+ continue
+ return out
+
+ def trace_line(self, line):
+ # strip newline and carriage return
+ line = line.rstrip('\n').rstrip('\r')
+
+ m = self.r_heap_size.search(line)
+ if m:
+ self.max = int(m.group(1))
+ self.mem = {}
+ self.allocated = 0
+ line += "\r\n\r\n\033[91m>> RESET HEAP COUNTERS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\033[0m\r\n"
+ line += "\033[4m (NUM) TOTAL [ ADDRESS] CHANGE (COMMENT)\033[0m\r\n"
+ return line
+
+ # match malloc, realloc and free
+ m = self.r_malloc.search(line)
+ if m:
+ out = ""
+ if m.group(2) == "0":
+ out += "\033[1m!! (%03d) \033[31mmalloc failed\033[0m (%s)\r\n" % (len(self.mem), line)
+ else:
+ self.mem[m.group(2)] = int(m.group(1))
+ self.allocated += int(m.group(1))
+ out += "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[31m+%-6d\033[0m (%s)\r\n" % \
+ (len(self.mem), self.allocated, int(m.group(2), 16), int(m.group(1)), line)
+ return out
+
+ m = self.r_free.search(line)
+ if m:
+ out = ""
+ freed = 0
+ if m.group(1) in self.mem:
+ freed = self.mem[m.group(1)]
+ self.allocated -= freed
+ del self.mem[m.group(1)]
+ else:
+ out += "\033[33m!! (%03d) WARN: free(0x%s)\033[0m\r\n" % (len(self.mem), m.group(4))
+ out += "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[92m-%-6d\033[0m (%s)\r\n" % \
+ (len(self.mem), self.allocated, int(m.group(1), 16), freed, line)
+ return out
+
+ # print all other lines as is, so we can still use the log functionality
+ return line + "\r\n"