Add display loop profiling

Probably the best place to root the profiler since we can get a better
top down view of bottlenecks in the graphics stack.

More,
- add in draft M4 downsampling code (commented) after getting it mostly
  working; next step is to move this processing into an FSP subactor.
- always update the vlm chart last y-axis sticky
- set call `.default_view()` just before inf sleep on startup
m4_corrections
Tyler Goodlet 2022-03-16 07:24:14 -04:00
parent bed52639d0
commit d86320848f
1 changed files with 106 additions and 41 deletions

View File

@ -29,6 +29,8 @@ from typing import Optional, Any, Callable
import numpy as np import numpy as np
import tractor import tractor
import trio import trio
import pyqtgraph as pg
from PyQt5.QtCore import QLineF
from .. import brokers from .. import brokers
from ..data.feed import open_feed from ..data.feed import open_feed
@ -178,7 +180,6 @@ async def graphics_update_loop(
vlm_sticky = vlm_chart._ysticks['volume'] vlm_sticky = vlm_chart._ysticks['volume']
maxmin = partial(chart_maxmin, chart, vlm_chart) maxmin = partial(chart_maxmin, chart, vlm_chart)
chart.default_view()
last_bars_range: tuple[float, float] last_bars_range: tuple[float, float]
( (
last_bars_range, last_bars_range,
@ -258,6 +259,8 @@ async def graphics_update_loop(
} }
}) })
chart.default_view()
# main loop # main loop
async for quotes in stream: async for quotes in stream:
ds.quotes = quotes ds.quotes = quotes
@ -295,6 +298,10 @@ def graphics_update_cycle(
# TODO: eventually optimize this whole graphics stack with ``numba`` # TODO: eventually optimize this whole graphics stack with ``numba``
# hopefully XD # hopefully XD
profiler = pg.debug.Profiler(
disabled=False, # not pg_profile_enabled(),
delayed=False,
)
# unpack multi-referenced components # unpack multi-referenced components
chart = ds.chart chart = ds.chart
vlm_chart = ds.vlm_chart vlm_chart = ds.vlm_chart
@ -305,6 +312,8 @@ def graphics_update_cycle(
vars = ds.vars vars = ds.vars
tick_margin = vars['tick_margin'] tick_margin = vars['tick_margin']
update_uppx = 5
for sym, quote in ds.quotes.items(): for sym, quote in ds.quotes.items():
# NOTE: vlm may be written by the ``brokerd`` backend # NOTE: vlm may be written by the ``brokerd`` backend
@ -319,10 +328,6 @@ def graphics_update_cycle(
# are diffed on each draw cycle anyway; so updates to the # are diffed on each draw cycle anyway; so updates to the
# "curve" length is already automatic. # "curve" length is already automatic.
# compute the first available graphic's x-units-per-pixel
xpx = vlm_chart.view.xs_in_px()
# print(r)
# increment the view position by the sample offset. # increment the view position by the sample offset.
i_step = ohlcv.index i_step = ohlcv.index
i_diff = i_step - vars['i_last'] i_diff = i_step - vars['i_last']
@ -338,8 +343,63 @@ def graphics_update_cycle(
l, lbar, rbar, r = brange l, lbar, rbar, r = brange
mx = mx_in_view + tick_margin mx = mx_in_view + tick_margin
mn = mn_in_view - tick_margin mn = mn_in_view - tick_margin
profiler('maxmin call')
liv = r > i_step # the last datum is in view liv = r > i_step # the last datum is in view
# compute the first available graphic's x-units-per-pixel
xpx = vlm_chart.view.xs_in_px()
in_view = chart.in_view(ohlcv.array)
if lbar != rbar:
# view box width in pxs
w = chart.view.boundingRect().width()
# TODO: a better way to get this?
# i would guess the esiest way is to just
# get the ``.boundingRect()`` of the curve
# in view but maybe there's something smarter?
# Currently we're just mapping the rbar, lbar to
# pixels via:
cw = chart.view.mapViewToDevice(QLineF(lbar, 0, rbar, 0)).length()
# is this faster?
# cw = chart.mapFromView(QLineF(lbar, 0 , rbar, 0)).length()
profiler(
f'view width pxs: {w}\n'
f'curve width pxs: {cw}\n'
f'sliced in view: {in_view.size}'
)
# compress bars to m4 line(s) if uppx is high enough
# if in_view.size > cw:
# from ._compression import ds_m4, hl2mxmn
# mxmn, x = hl2mxmn(in_view)
# profiler('hl tracer')
# nb, x, y = ds_m4(
# x=x,
# y=mxmn,
# # TODO: this needs to actually be the width
# # in pixels of the visible curve since we don't
# # want to downsample any 'zeros' around the curve,
# # just the values that make up the curve graphic,
# # i think?
# px_width=cw,
# )
# profiler(
# 'm4 downsampled\n'
# f' ds bins: {nb}\n'
# f' x.shape: {x.shape}\n'
# f' y.shape: {y.shape}\n'
# f' x: {x}\n'
# f' y: {y}\n'
# )
# breakpoint()
# assert y.size == mxmn.size
# don't real-time "shift" the curve to the # don't real-time "shift" the curve to the
# left unless we get one of the following: # left unless we get one of the following:
if ( if (
@ -355,17 +415,18 @@ def graphics_update_cycle(
# and then iff update curves and shift? # and then iff update curves and shift?
chart.increment_view(steps=i_diff) chart.increment_view(steps=i_diff)
if ( if vlm_chart:
vlm_chart # always update y-label
# if zoomed out alot don't update the last "bar"
and xpx < 4
):
vlm_chart.update_curve_from_array('volume', array)
ds.vlm_sticky.update_from_data(*array[-1][['index', 'volume']]) ds.vlm_sticky.update_from_data(*array[-1][['index', 'volume']])
if ( if (
mx_vlm_in_view > vars['last_mx_vlm'] (xpx < update_uppx or i_diff > 0)
or trigger_all or trigger_all
):
vlm_chart.update_curve_from_array('volume', array)
if (
mx_vlm_in_view != vars['last_mx_vlm']
): ):
# print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}') # print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}')
vlm_chart.view._set_yrange( vlm_chart.view._set_yrange(
@ -431,8 +492,10 @@ def graphics_update_cycle(
# for typ, tick in reversed(lasts.items()): # for typ, tick in reversed(lasts.items()):
# update ohlc sampled price bars # update ohlc sampled price bars
if (
if xpx < 4 or i_diff > 0: xpx < update_uppx
or i_diff > 0
):
chart.update_ohlc_from_array( chart.update_ohlc_from_array(
chart.name, chart.name,
array, array,
@ -600,8 +663,8 @@ async def display_symbol_data(
f'step:1s ' f'step:1s '
) )
linkedsplits = godwidget.linkedsplits linked = godwidget.linkedsplits
linkedsplits._symbol = symbol linked._symbol = symbol
# generate order mode side-pane UI # generate order mode side-pane UI
# A ``FieldsForm`` form to configure order entry # A ``FieldsForm`` form to configure order entry
@ -611,7 +674,7 @@ async def display_symbol_data(
godwidget.pp_pane = pp_pane godwidget.pp_pane = pp_pane
# create main OHLC chart # create main OHLC chart
chart = linkedsplits.plot_ohlc_main( chart = linked.plot_ohlc_main(
symbol, symbol,
bars, bars,
sidepane=pp_pane, sidepane=pp_pane,
@ -641,8 +704,8 @@ async def display_symbol_data(
# NOTE: we must immediately tell Qt to show the OHLC chart # NOTE: we must immediately tell Qt to show the OHLC chart
# to avoid a race where the subplots get added/shown to # to avoid a race where the subplots get added/shown to
# the linked set *before* the main price chart! # the linked set *before* the main price chart!
linkedsplits.show() linked.show()
linkedsplits.focus() linked.focus()
await trio.sleep(0) await trio.sleep(0)
vlm_chart: Optional[ChartPlotWidget] = None vlm_chart: Optional[ChartPlotWidget] = None
@ -652,7 +715,7 @@ async def display_symbol_data(
if has_vlm(ohlcv): if has_vlm(ohlcv):
vlm_chart = await ln.start( vlm_chart = await ln.start(
open_vlm_displays, open_vlm_displays,
linkedsplits, linked,
ohlcv, ohlcv,
) )
@ -660,7 +723,7 @@ async def display_symbol_data(
# from an input config. # from an input config.
ln.start_soon( ln.start_soon(
start_fsp_displays, start_fsp_displays,
linkedsplits, linked,
ohlcv, ohlcv,
loading_sym_key, loading_sym_key,
loglevel, loglevel,
@ -669,7 +732,7 @@ async def display_symbol_data(
# start graphics update loop after receiving first live quote # start graphics update loop after receiving first live quote
ln.start_soon( ln.start_soon(
graphics_update_loop, graphics_update_loop,
linkedsplits, linked,
feed.stream, feed.stream,
ohlcv, ohlcv,
wap_in_history, wap_in_history,
@ -687,17 +750,19 @@ async def display_symbol_data(
# let Qt run to render all widgets and make sure the # let Qt run to render all widgets and make sure the
# sidepanes line up vertically. # sidepanes line up vertically.
await trio.sleep(0) await trio.sleep(0)
linkedsplits.resize_sidepanes() linked.resize_sidepanes()
# NOTE: we pop the volume chart from the subplots set so # NOTE: we pop the volume chart from the subplots set so
# that it isn't double rendered in the display loop # that it isn't double rendered in the display loop
# above since we do a maxmin calc on the volume data to # above since we do a maxmin calc on the volume data to
# determine if auto-range adjustements should be made. # determine if auto-range adjustements should be made.
linkedsplits.subplots.pop('volume', None) linked.subplots.pop('volume', None)
# TODO: make this not so shit XD # TODO: make this not so shit XD
# close group status # close group status
sbar._status_groups[loading_sym_key][1]() sbar._status_groups[loading_sym_key][1]()
# let the app run.. bby # let the app run.. bby
chart.default_view()
# linked.graphics_cycle()
await trio.sleep_forever() await trio.sleep_forever()