Pass performance data to UI.

looper
Ben Niemann 7 years ago
parent 55145ed2dc
commit 7bec71cf5b

@ -19,14 +19,15 @@ class AudioProcClientMixin(object):
self.server.add_command_handler(
'PIPELINE_MUTATION', self.handle_pipeline_mutation)
self.server.add_command_handler(
'PIPELINE_STATUS', self.handle_pipeline_status)
'PIPELINE_STATUS', self.handle_pipeline_status,
log_level=logging.DEBUG)
async def connect(self, address):
async def connect(self, address, flags=None):
assert self._stub is None
self._stub = ipc.Stub(self.event_loop, address)
await self._stub.connect()
self._session_id = await self._stub.call(
'START_SESSION', self.server.address)
'START_SESSION', self.server.address, flags)
async def disconnect(self, shutdown=False):
if self._session_id is not None:

@ -26,9 +26,10 @@ class InvalidSessionError(Exception): pass
class Session(object):
def __init__(self, event_loop, callback_stub):
def __init__(self, event_loop, callback_stub, flags):
self.event_loop = event_loop
self.callback_stub = callback_stub
self.flags = flags or set()
self.id = uuid.uuid4().hex
self.pending_mutations = []
@ -52,9 +53,15 @@ class Session(object):
"PUBLISH_MUTATION failed with exception: %s", exc)
def publish_status(self, status):
callback_task = self.event_loop.create_task(
self.callback_stub.call('PIPELINE_STATUS', status))
callback_task.add_done_callback(self.publish_status_done)
status = dict(status)
if 'perf_data' not in self.flags and 'perf_data' in status:
del status['perf_data']
if status:
callback_task = self.event_loop.create_task(
self.callback_stub.call('PIPELINE_STATUS', status))
callback_task.add_done_callback(self.publish_status_done)
def publish_status_done(self, callback_task):
assert callback_task.done()
@ -118,6 +125,7 @@ class AudioProcProcessMixin(object):
self.pipeline = pipeline.Pipeline()
self.pipeline.utilization_callback = self.utilization_callback
self.pipeline.listeners.add('perf_data', self.perf_data_callback)
self.backend = None
@ -168,10 +176,10 @@ class AudioProcProcessMixin(object):
for session in self.sessions.values():
session.publish_status(kwargs)
def handle_start_session(self, client_address):
def handle_start_session(self, client_address, flags):
client_stub = ipc.Stub(self.event_loop, client_address)
connect_task = self.event_loop.create_task(client_stub.connect())
session = Session(self.event_loop, client_stub)
session = Session(self.event_loop, client_stub, flags)
connect_task.add_done_callback(
functools.partial(self._client_connected, session))
self.sessions[session.id] = session
@ -275,6 +283,11 @@ class AudioProcProcessMixin(object):
self.pipeline.set_backend(be)
return result
def perf_data_callback(self, perf_data):
self.event_loop.call_soon_threadsafe(
functools.partial(
self.publish_status, perf_data=perf_data))
async def handle_play_file(self, session_id, path):
self.get_session(session_id)

@ -14,6 +14,9 @@ import uuid
import pyaudio
from noisicaa import music
from noisicaa import core
from .resample import (Resampler,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
@ -22,7 +25,6 @@ from .node import Node
from .node_types import NodeType
from .ports import AudioInputPort, EventOutputPort
from . import events
from .. import music
from . import audio_format
from . import frame
from . import audio_stream
@ -208,12 +210,6 @@ class PyAudioBackend(Backend):
self._need_more.clear()
self.clear_events()
# TODO: backend should write data here, not when called
# from the sink node.
# And get perf data - IPCBackend should serialize
# perf data and send it upstream.
print('\n'.join(str(s) for s in ctxt.perf.get_spans()))
class Stopped(Exception):
pass
@ -261,7 +257,10 @@ class IPCBackend(Backend):
def write(self, ctxt):
assert ctxt.out_frame is not None
assert ctxt.perf.current_span_id == 0
perf_data = ctxt.perf.get_spans()
ctxt.out_frame.timepos += self.timepos_offset
ctxt.out_frame.perf_data = ctxt.perf.get_spans()
ctxt.out_frame.perf_data = perf_data
self._stream.send_frame(ctxt.out_frame)
self.clear_events()

@ -35,6 +35,8 @@ class Pipeline(object):
self._notifications = []
self.notification_listener = core.CallbackRegistry()
self.listeners = core.CallbackRegistry()
def reader_lock(self):
return self._lock.reader_lock
@ -144,6 +146,7 @@ class Pipeline(object):
# self.utilization_callback(utilization)
backend.write(ctxt)
self.listeners.call('perf_data', ctxt.perf.get_spans())
ctxt.timepos += 4096
except: # pylint: disable=bare-except

@ -116,10 +116,13 @@ class Server(object):
self._server = None
self._command_handlers = {}
self._command_log_levels = {}
def add_command_handler(self, cmd, handler):
def add_command_handler(self, cmd, handler, log_level=None):
assert cmd not in self._command_handlers
self._command_handlers[cmd] = handler
if log_level is not None:
self._command_log_levels[cmd] = log_level
def new_connection_id(self):
self._next_connection_id += 1
@ -162,7 +165,9 @@ class Server(object):
handler = self._command_handlers[command]
args, kwargs = self.deserialize(payload)
logger.info(
logger.log(
self._command_log_levels.get(command, logging.INFO),
"%s(%s%s)",
command,
', '.join(str(a) for a in args),

@ -24,7 +24,7 @@ from .editor_window import EditorWindow
from ..instr import library
from . import project_registry
from . import pipeline_perf_monitor
logger = logging.getLogger('ui.editor_app')
@ -70,8 +70,14 @@ class AudioProcClientImpl(object):
async def cleanup(self):
pass
class AudioProcClient(audioproc.AudioProcClientMixin, AudioProcClientImpl):
pass
class AudioProcClient(
audioproc.AudioProcClientMixin, AudioProcClientImpl):
def __init__(self, app, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__app = app
def handle_pipeline_status(self, status):
self.__app.onPipelineStatus(status)
class BaseEditorApp(QApplication):
@ -200,6 +206,9 @@ class BaseEditorApp(QApplication):
self._updateOpenedProjects()
await self.project_registry.close_project(project_connection)
def onPipelineStatus(self, status):
pass
class EditorApp(BaseEditorApp):
def __init__(self, process, runtime_settings, paths, settings=None):
@ -209,6 +218,7 @@ class EditorApp(BaseEditorApp):
self._old_excepthook = None
self.win = None
self._pipeline_perf_monitor = None
async def setup(self):
logger.info("Installing custom excepthook.")
@ -225,6 +235,9 @@ class EditorApp(BaseEditorApp):
await self.win.setup()
self.win.show()
self._pipeline_perf_monitor = pipeline_perf_monitor.PipelinePerfMonitor(self, self.win)
self._pipeline_perf_monitor.show()
if self.paths:
logger.info("Starting with projects from cmdline.")
for path in self.paths:
@ -253,6 +266,10 @@ class EditorApp(BaseEditorApp):
self.dumpSettings()
async def cleanup(self):
if self._pipeline_perf_monitor is not None:
self._pipeline_perf_monitor.storeState()
self._pipeline_perf_monitor = None
if self.win is not None:
await self.win.cleanup()
self.win = None
@ -274,9 +291,18 @@ class EditorApp(BaseEditorApp):
'CREATE_AUDIOPROC_PROCESS', 'main')
self.audioproc_client = AudioProcClient(
self.process.event_loop, self.process.server)
self, self.process.event_loop, self.process.server)
await self.audioproc_client.setup()
await self.audioproc_client.connect(self.audioproc_process)
await self.audioproc_client.connect(
self.audioproc_process, {'perf_data'})
await self.audioproc_client.set_backend(
self.settings.value('audio/backend', 'pyaudio'))
def onPipelineStatus(self, status):
if 'utilization' in status:
pass
if 'perf_data' in status:
if self._pipeline_perf_monitor is not None:
self._pipeline_perf_monitor.addPerfData(
status['perf_data'])

@ -0,0 +1,41 @@
#!/usr/bin/python
import os.path
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from . import ui_base
class PipelinePerfMonitor(ui_base.CommonMixin, QtWidgets.QDialog):
def __init__(self, app, parent):
super().__init__(app=app, parent=parent)
self.setWindowTitle("noisicaä - Pipeline Performance Monitor")
self.resize(600, 300)
self.bla = QtWidgets.QTextEdit(self)
self.bla.setReadOnly(True)
self.bla.setFont(QtGui.QFont('Courier New', 10))
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.bla)
self.setLayout(layout)
self.setVisible(
int(self.app.settings.value(
'dialog/pipeline_perf_monitor/visible', False)))
self.restoreGeometry(
self.app.settings.value(
'dialog/pipeline_perf_monitor/geometry', b''))
def storeState(self):
s = self.app.settings
s.beginGroup('dialog/pipeline_perf_monitor')
s.setValue('visible', int(self.isVisible()))
s.setValue('geometry', self.saveGeometry())
s.endGroup()
def addPerfData(self, perf_data):
self.bla.setPlainText('\n'.join(str(s) for s in perf_data))
Loading…
Cancel
Save