forked from Imagelibrary/littlefs
Reworked/simplified tracebd.py a bit
Instead of trying to align to block-boundaries tracebd.py now just aliases to whatever dimensions are provided. Also reworked how scripts handle default sizing. Now using reasonable defaults with 0 being a placeholder for automatic sizing. The addition of -z/--cat makes it possible to pipe directly to stdout. Also added support for dots/braille output which can capture more detail, though care needs to be taken to not rely on accurate coloring.
This commit is contained in:
127
scripts/plot.py
127
scripts/plot.py
@@ -89,6 +89,61 @@ def openio(path, mode='r'):
|
|||||||
else:
|
else:
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
|
|
||||||
|
class LinesIO:
|
||||||
|
def __init__(self, maxlen=None):
|
||||||
|
self.maxlen = maxlen
|
||||||
|
self.lines = co.deque(maxlen=maxlen)
|
||||||
|
self.tail = io.StringIO()
|
||||||
|
|
||||||
|
# trigger automatic sizing
|
||||||
|
if maxlen == 0:
|
||||||
|
self.resize(0)
|
||||||
|
|
||||||
|
def write(self, s):
|
||||||
|
# note using split here ensures the trailing string has no newline
|
||||||
|
lines = s.split('\n')
|
||||||
|
|
||||||
|
if len(lines) > 1 and self.tail.getvalue():
|
||||||
|
self.tail.write(lines[0])
|
||||||
|
lines[0] = self.tail.getvalue()
|
||||||
|
self.tail = io.StringIO()
|
||||||
|
|
||||||
|
self.lines.extend(lines[:-1])
|
||||||
|
|
||||||
|
if lines[-1]:
|
||||||
|
self.tail.write(lines[-1])
|
||||||
|
|
||||||
|
def resize(self, maxlen):
|
||||||
|
self.maxlen = maxlen
|
||||||
|
if maxlen == 0:
|
||||||
|
maxlen = shutil.get_terminal_size((80, 5))[1]
|
||||||
|
if maxlen != self.lines.maxlen:
|
||||||
|
self.lines = co.deque(self.lines, maxlen=maxlen)
|
||||||
|
|
||||||
|
last_lines = 1
|
||||||
|
def draw(self):
|
||||||
|
# did terminal size change?
|
||||||
|
if self.maxlen == 0:
|
||||||
|
self.resize(0)
|
||||||
|
|
||||||
|
# first thing first, give ourself a canvas
|
||||||
|
while LinesIO.last_lines < len(self.lines):
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
LinesIO.last_lines += 1
|
||||||
|
|
||||||
|
for j, line in enumerate(self.lines):
|
||||||
|
# move cursor, clear line, disable/reenable line wrapping
|
||||||
|
sys.stdout.write('\r')
|
||||||
|
if len(self.lines)-1-j > 0:
|
||||||
|
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-j))
|
||||||
|
sys.stdout.write('\x1b[K')
|
||||||
|
sys.stdout.write('\x1b[?7l')
|
||||||
|
sys.stdout.write(line)
|
||||||
|
sys.stdout.write('\x1b[?7h')
|
||||||
|
if len(self.lines)-1-j > 0:
|
||||||
|
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-j))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
# parse different data representations
|
# parse different data representations
|
||||||
def dat(x):
|
def dat(x):
|
||||||
@@ -114,6 +169,7 @@ def dat(x):
|
|||||||
# else give up
|
# else give up
|
||||||
raise ValueError("invalid dat %r" % x)
|
raise ValueError("invalid dat %r" % x)
|
||||||
|
|
||||||
|
|
||||||
# a hack log10 that preserves sign, and passes zero as zero
|
# a hack log10 that preserves sign, and passes zero as zero
|
||||||
def slog10(x):
|
def slog10(x):
|
||||||
if x == 0:
|
if x == 0:
|
||||||
@@ -123,7 +179,6 @@ def slog10(x):
|
|||||||
else:
|
else:
|
||||||
return -m.log10(-x)
|
return -m.log10(-x)
|
||||||
|
|
||||||
|
|
||||||
class Plot:
|
class Plot:
|
||||||
def __init__(self, width, height, *,
|
def __init__(self, width, height, *,
|
||||||
xlim=None,
|
xlim=None,
|
||||||
@@ -427,7 +482,8 @@ def main(csv_paths, *,
|
|||||||
xlim=None,
|
xlim=None,
|
||||||
ylim=None,
|
ylim=None,
|
||||||
width=None,
|
width=None,
|
||||||
height=None,
|
height=17,
|
||||||
|
cat=False,
|
||||||
color=False,
|
color=False,
|
||||||
braille=False,
|
braille=False,
|
||||||
colors=None,
|
colors=None,
|
||||||
@@ -538,10 +594,12 @@ def main(csv_paths, *,
|
|||||||
if v is not None))))
|
if v is not None))))
|
||||||
|
|
||||||
# figure out our plot size
|
# figure out our plot size
|
||||||
if width is not None:
|
if width is None:
|
||||||
|
width_ = min(80, shutil.get_terminal_size((80, 17))[0])
|
||||||
|
elif width:
|
||||||
width_ = width
|
width_ = width
|
||||||
else:
|
else:
|
||||||
width_ = shutil.get_terminal_size((80, 8))[0]
|
width_ = shutil.get_terminal_size((80, 17))[0]
|
||||||
# make space for units
|
# make space for units
|
||||||
width_ -= 5
|
width_ -= 5
|
||||||
# make space for legend
|
# make space for legend
|
||||||
@@ -550,10 +608,10 @@ def main(csv_paths, *,
|
|||||||
# limit a bit
|
# limit a bit
|
||||||
width_ = max(2*4, width_)
|
width_ = max(2*4, width_)
|
||||||
|
|
||||||
if height is not None:
|
if height:
|
||||||
height_ = height
|
height_ = height
|
||||||
else:
|
else:
|
||||||
height_ = shutil.get_terminal_size((80, 8))[1]
|
height_ = shutil.get_terminal_size((80, 17))[1]
|
||||||
# make space for shell prompt
|
# make space for shell prompt
|
||||||
if not keep_open:
|
if not keep_open:
|
||||||
height_ -= 1
|
height_ -= 1
|
||||||
@@ -644,45 +702,26 @@ def main(csv_paths, *,
|
|||||||
'\x1b[m' if color else '')
|
'\x1b[m' if color else '')
|
||||||
for j in range(i, min(i+legend_cols, len(legend_))))))
|
for j in range(i, min(i+legend_cols, len(legend_))))))
|
||||||
|
|
||||||
|
|
||||||
last_lines = 1
|
|
||||||
def redraw():
|
|
||||||
nonlocal last_lines
|
|
||||||
|
|
||||||
canvas = io.StringIO()
|
|
||||||
draw(canvas)
|
|
||||||
canvas = canvas.getvalue().splitlines()
|
|
||||||
|
|
||||||
# give ourself a canvas
|
|
||||||
while last_lines < len(canvas):
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
last_lines += 1
|
|
||||||
|
|
||||||
for i, line in enumerate(canvas):
|
|
||||||
jump = len(canvas)-1-i
|
|
||||||
# move cursor, clear line, disable/reenable line wrapping
|
|
||||||
sys.stdout.write('\r')
|
|
||||||
if jump > 0:
|
|
||||||
sys.stdout.write('\x1b[%dA' % jump)
|
|
||||||
sys.stdout.write('\x1b[K')
|
|
||||||
sys.stdout.write('\x1b[?7l')
|
|
||||||
sys.stdout.write(line)
|
|
||||||
sys.stdout.write('\x1b[?7h')
|
|
||||||
if jump > 0:
|
|
||||||
sys.stdout.write('\x1b[%dB' % jump)
|
|
||||||
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
if keep_open:
|
if keep_open:
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
redraw()
|
if cat:
|
||||||
|
draw(sys.stdout)
|
||||||
|
else:
|
||||||
|
ring = LinesIO()
|
||||||
|
draw(ring)
|
||||||
|
ring.draw()
|
||||||
# don't just flood open calls
|
# don't just flood open calls
|
||||||
time.sleep(sleep or 0.1)
|
time.sleep(sleep or 0.1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
redraw()
|
if cat:
|
||||||
|
draw(sys.stdout)
|
||||||
|
else:
|
||||||
|
ring = LinesIO()
|
||||||
|
draw(ring)
|
||||||
|
ring.draw()
|
||||||
sys.stdout.write('\n')
|
sys.stdout.write('\n')
|
||||||
else:
|
else:
|
||||||
draw(sys.stdout)
|
draw(sys.stdout)
|
||||||
@@ -726,9 +765,9 @@ if __name__ == "__main__":
|
|||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--braille',
|
'-⣿', '--braille',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use unicode braille characters. Note that braille characters "
|
help="Use 2x4 unicode braille characters. Note that braille characters "
|
||||||
"sometimes suffer from inconsistent widths.")
|
"sometimes suffer from inconsistent widths.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--colors',
|
'--colors',
|
||||||
@@ -747,12 +786,16 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-W', '--width',
|
'-W', '--width',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Width in columns. A width of 0 indicates no limit. Defaults "
|
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||||
"to terminal width or 80.")
|
"min(terminal, 80).")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-H', '--height',
|
'-H', '--height',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Height in rows. Defaults to terminal height or 8.")
|
help="Height in rows. 0 uses the terminal height. Defaults to 17.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-z', '--cat',
|
||||||
|
action='store_true',
|
||||||
|
help="Pipe directly to stdout.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-X', '--xlim',
|
'-X', '--xlim',
|
||||||
type=lambda x: tuple(dat(x) if x else None for x in x.split(',')),
|
type=lambda x: tuple(dat(x) if x else None for x in x.split(',')),
|
||||||
|
|||||||
@@ -9,9 +9,11 @@
|
|||||||
# SPDX-License-Identifier: BSD-3-Clause
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import collections as co
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import threading as th
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
@@ -24,71 +26,89 @@ def openio(path, mode='r'):
|
|||||||
else:
|
else:
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
|
|
||||||
def main(path='-', *, lines=1, sleep=0.01, keep_open=False):
|
class LinesIO:
|
||||||
ring = [None] * lines
|
def __init__(self, maxlen=None):
|
||||||
i = 0
|
self.maxlen = maxlen
|
||||||
count = 0
|
self.lines = co.deque(maxlen=maxlen)
|
||||||
lock = th.Lock()
|
self.tail = io.StringIO()
|
||||||
event = th.Event()
|
|
||||||
done = False
|
|
||||||
|
|
||||||
# do the actual reading in a background thread
|
# trigger automatic sizing
|
||||||
def read():
|
if maxlen == 0:
|
||||||
nonlocal i
|
self.resize(0)
|
||||||
nonlocal count
|
|
||||||
nonlocal done
|
def write(self, s):
|
||||||
|
# note using split here ensures the trailing string has no newline
|
||||||
|
lines = s.split('\n')
|
||||||
|
|
||||||
|
if len(lines) > 1 and self.tail.getvalue():
|
||||||
|
self.tail.write(lines[0])
|
||||||
|
lines[0] = self.tail.getvalue()
|
||||||
|
self.tail = io.StringIO()
|
||||||
|
|
||||||
|
self.lines.extend(lines[:-1])
|
||||||
|
|
||||||
|
if lines[-1]:
|
||||||
|
self.tail.write(lines[-1])
|
||||||
|
|
||||||
|
def resize(self, maxlen):
|
||||||
|
self.maxlen = maxlen
|
||||||
|
if maxlen == 0:
|
||||||
|
maxlen = shutil.get_terminal_size((80, 5))[1]
|
||||||
|
if maxlen != self.lines.maxlen:
|
||||||
|
self.lines = co.deque(self.lines, maxlen=maxlen)
|
||||||
|
|
||||||
|
last_lines = 1
|
||||||
|
def draw(self):
|
||||||
|
# did terminal size change?
|
||||||
|
if self.maxlen == 0:
|
||||||
|
self.resize(0)
|
||||||
|
|
||||||
|
# first thing first, give ourself a canvas
|
||||||
|
while LinesIO.last_lines < len(self.lines):
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
LinesIO.last_lines += 1
|
||||||
|
|
||||||
|
for j, line in enumerate(self.lines):
|
||||||
|
# move cursor, clear line, disable/reenable line wrapping
|
||||||
|
sys.stdout.write('\r')
|
||||||
|
if len(self.lines)-1-j > 0:
|
||||||
|
sys.stdout.write('\x1b[%dA' % (len(self.lines)-1-j))
|
||||||
|
sys.stdout.write('\x1b[K')
|
||||||
|
sys.stdout.write('\x1b[?7l')
|
||||||
|
sys.stdout.write(line)
|
||||||
|
sys.stdout.write('\x1b[?7h')
|
||||||
|
if len(self.lines)-1-j > 0:
|
||||||
|
sys.stdout.write('\x1b[%dB' % (len(self.lines)-1-j))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def main(path='-', *, lines=5, cat=False, sleep=0.01, keep_open=False):
|
||||||
|
if cat:
|
||||||
|
ring = sys.stdout
|
||||||
|
else:
|
||||||
|
ring = LinesIO(lines)
|
||||||
|
|
||||||
|
ptime = time.time()
|
||||||
|
try:
|
||||||
while True:
|
while True:
|
||||||
with openio(path) as f:
|
with openio(path) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
with lock:
|
ring.write(line)
|
||||||
ring[i] = line
|
|
||||||
i = (i + 1) % lines
|
# need to redraw?
|
||||||
count = min(lines, count + 1)
|
if not cat and time.time()-ptime >= sleep:
|
||||||
event.set()
|
ring.draw()
|
||||||
|
ptime = time.time()
|
||||||
|
|
||||||
if not keep_open:
|
if not keep_open:
|
||||||
break
|
break
|
||||||
# don't just flood open calls
|
# don't just flood open calls
|
||||||
time.sleep(sleep or 0.1)
|
time.sleep(sleep or 0.1)
|
||||||
done = True
|
|
||||||
|
|
||||||
th.Thread(target=read, daemon=True).start()
|
|
||||||
|
|
||||||
try:
|
|
||||||
last_count = 1
|
|
||||||
while not done:
|
|
||||||
time.sleep(sleep)
|
|
||||||
event.wait()
|
|
||||||
event.clear()
|
|
||||||
|
|
||||||
# create a copy to avoid corrupt output
|
|
||||||
with lock:
|
|
||||||
ring_ = ring.copy()
|
|
||||||
i_ = i
|
|
||||||
count_ = count
|
|
||||||
|
|
||||||
# first thing first, give ourself a canvas
|
|
||||||
while last_count < count_:
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
last_count += 1
|
|
||||||
|
|
||||||
for j in range(count_):
|
|
||||||
# move cursor, clear line, disable/reenable line wrapping
|
|
||||||
sys.stdout.write('\r')
|
|
||||||
if count_-1-j > 0:
|
|
||||||
sys.stdout.write('\x1b[%dA' % (count_-1-j))
|
|
||||||
sys.stdout.write('\x1b[K')
|
|
||||||
sys.stdout.write('\x1b[?7l')
|
|
||||||
sys.stdout.write(ring_[(i_-count_+j) % lines][:-1])
|
|
||||||
sys.stdout.write('\x1b[?7h')
|
|
||||||
if count_-1-j > 0:
|
|
||||||
sys.stdout.write('\x1b[%dB' % (count_-1-j))
|
|
||||||
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
sys.stdout.write('\n')
|
if not cat:
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -104,15 +124,18 @@ if __name__ == "__main__":
|
|||||||
'-n',
|
'-n',
|
||||||
'--lines',
|
'--lines',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Number of lines to show. Defaults to 1.")
|
help="Show this many lines of history. 0 uses the terminal height. "
|
||||||
|
"Defaults to 5.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s',
|
'-z', '--cat',
|
||||||
'--sleep',
|
action='store_true',
|
||||||
|
help="Pipe directly to stdout.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--sleep',
|
||||||
type=float,
|
type=float,
|
||||||
help="Seconds to sleep between reads. Defaults to 0.01.")
|
help="Seconds to sleep between reads. Defaults to 0.01.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-k',
|
'-k', '--keep-open',
|
||||||
'--keep-open',
|
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Reopen the pipe on EOF, useful when multiple "
|
help="Reopen the pipe on EOF, useful when multiple "
|
||||||
"processes are writing.")
|
"processes are writing.")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user