From d6c17390f0e4199d094e4af3a8a5e97c98f2c579 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 1 Mar 2017 21:10:09 -0500 Subject: [PATCH] Make vnc_playback.html functional once more This commit makes vnc_playback.html functional once more, and completely refactors tests/playback.js to make it usable in other scenarios. In order for vnc_playback.js to properly load playback files now, they must `export` their variables. --- tests/playback-ui.js | 174 +++++++++++ tests/playback.js | 279 +++++++++--------- tests/vnc_playback.html | 100 +------ .../dist/browser-es-module-loader.js | 8 +- .../src/browser-es-module-loader.js | 8 +- 5 files changed, 330 insertions(+), 239 deletions(-) create mode 100644 tests/playback-ui.js diff --git a/tests/playback-ui.js b/tests/playback-ui.js new file mode 100644 index 0000000..f47b9d8 --- /dev/null +++ b/tests/playback-ui.js @@ -0,0 +1,174 @@ +import * as WebUtil from '../app/webutil.js'; +import RecordingPlayer from './playback.js'; + +var frames = null; +var encoding = null; + +function message(str) { + console.log(str); + var cell = document.getElementById('messages'); + cell.textContent += str + "\n"; + cell.scrollTop = cell.scrollHeight; +} + +function loadFile() { + const fname = WebUtil.getQueryVar('data', null); + + if (!fname) { + return Promise.reject("Must specify data=FOO in query string."); + } + + message("Loading " + fname); + return import(`../recordings/${fname}#nocache`); +} + +function enableUI(recording) { + var iterations = WebUtil.getQueryVar('iterations', 3); + document.getElementById('iterations').value = iterations; + + var mode = WebUtil.getQueryVar('mode', 3); + if (mode === 'realtime') { + document.getElementById('mode2').checked = true; + } else { + document.getElementById('mode1').checked = true; + } + + message("VNC_frame_data.length: " + recording.VNC_frame_data.length); + + const startButton = document.getElementById('startButton'); + startButton.disabled = false + startButton.addEventListener('click', start); + + frames = recording.VNC_frame_data; + encoding = recording.VNC_frame_encoding; +} + +const notification = function (rfb, mesg, level, options) { + document.getElementById('VNC_status').textContent = mesg; +} + +function IterationPlayer (iterations, frames, encoding) { + this._iterations = iterations; + + this._iteration = undefined; + this._player = undefined; + + this._start_time = undefined; + + this._frames = frames; + this._encoding = encoding; + + this._state = 'running'; + + this.onfinish = function() {}; + this.oniterationfinish = function() {}; + this.rfbdisconnected = function() {}; + this.rfbnotification = function() {}; +} + +IterationPlayer.prototype = { + start: function (mode) { + this._iteration = 0; + this._start_time = (new Date()).getTime(); + + this._realtime = mode.startsWith('realtime'); + this._trafficMgmt = !mode.endsWith('-no-mgmt'); + + this._nextIteration(); + }, + + _nextIteration: function () { + const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this), this._notification.bind(this)); + player.onfinish = this._iterationFinish.bind(this); + + if (this._state !== 'running') { return; } + + this._iteration++; + if (this._iteration > this._iterations) { + this._finish(); + return; + } + + player.run(this._realtime, this._trafficMgmt); + }, + + _finish: function () { + const endTime = (new Date()).getTime(); + const totalDuration = endTime - this._start_time; + + const evt = new Event('finish'); + evt.duration = totalDuration; + evt.iterations = this._iterations; + this.onfinish(evt); + }, + + _iterationFinish: function (duration) { + const evt = new Event('iterationfinish'); + evt.duration = duration; + evt.number = this._iteration; + this.oniterationfinish(evt); + + this._nextIteration(); + }, + + _disconnected: function (rfb, reason, frame) { + if (reason) { + this._state = 'failed'; + } + + var evt = new Event('rfbdisconnected'); + evt.reason = reason; + evt.frame = frame; + + this.onrfbdisconnected(evt); + }, + + _notification: function (rfb, msg, level, options) { + var evt = new Event('rfbnotification'); + evt.message = msg; + evt.level = level; + evt.options = options; + + this.onrfbnotification(evt); + }, +}; + +function start() { + document.getElementById('startButton').value = "Running"; + document.getElementById('startButton').disabled = true; + + const iterations = document.getElementById('iterations').value; + + var mode; + + if (document.getElementById('mode1').checked) { + message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`); + mode = 'perftest'; + } else { + message(`Starting realtime playback [${iterations} iteration(s)]`); + mode = 'realtime'; + } + + const player = new IterationPlayer(iterations, frames, encoding); + player.oniterationfinish = function (evt) { + message(`Iteration ${evt.number} took ${evt.duration}ms`); + }; + player.onrfbdisconnected = function (evt) { + if (evt.reason) { + message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`); + } + }; + player.onrfbnotification = function (evt) { + document.getElementById('VNC_status').textContent = evt.message; + }; + player.onfinish = function (evt) { + const iterTime = parseInt(evt.duration / evt.iterations, 10); + message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`); + + document.getElementById('startButton').disabled = false; + document.getElementById('startButton').value = "Start"; + }; + player.start(mode); +} + +loadFile().then(enableUI).catch(message); diff --git a/tests/playback.js b/tests/playback.js index a25fac4..b39c981 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -4,29 +4,17 @@ * Licensed under MPL 2.0 (see LICENSE.txt) */ -"use strict"; -/*jslint browser: true, white: false */ -/*global Util, VNC_frame_data, finish */ - -var rfb, mode, test_state, frame_idx, frame_length, - iteration, iterations, istart_time, encoding, - - // Pre-declarations for jslint - send_array, next_iteration, end_iteration, queue_next_packet, - do_packet, enable_test_mode; - -// Override send_array -send_array = function (arr) { - // Stub out send_array -}; +import RFB from '../core/rfb.js'; +import * as Log from '../core/util/logging.js'; +import Base64 from '../core/base64.js'; // Immediate polyfill -if (window.setImmediate === undefined) { +if (setImmediate === undefined) { var _immediateIdCounter = 1; var _immediateFuncs = {}; - window.setImmediate = function (func) { - var index = Util._immediateIdCounter++; + var setImmediate = function (func) { + var index = _immediateIdCounter++; _immediateFuncs[index] = func; window.postMessage("noVNC immediate trigger:" + index, "*"); return index; @@ -56,143 +44,160 @@ if (window.setImmediate === undefined) { window.addEventListener("message", _onMessage); } -enable_test_mode = function () { - rfb._sock.send = send_array; - rfb._sock.close = function () {}; - rfb._sock.flush = function () {}; - rfb._checkEvents = function () {}; - rfb.connect = function (host, port, password, path) { - this._rfb_host = host; - this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; - this._rfb_path = (path !== undefined) ? path : ""; - this._sock.init('binary', 'ws'); - this._rfb_connection_state = 'connecting'; - this._rfb_init_state = 'ProtocolVersion'; - }; -}; +export default function RecordingPlayer (frames, encoding, disconnected, notification) { + this._frames = frames; + this._encoding = encoding; -next_iteration = function () { - rfb = new RFB({'target': document.getElementById('VNC_canvas'), - 'view_only': true, - 'onDisconnected': disconnected, - 'onNotification': notification}); - enable_test_mode(); + this._disconnected = disconnected; + this._notification = notification; - // Missing in older recordings - if (typeof VNC_frame_encoding === 'undefined') { - var frame = VNC_frame_data[0]; - var start = frame.indexOf('{', 1) + 1; + if (this._encoding === undefined) { + let frame = this._frames[0]; + let start = frame.indexOf('{', 1) + 1; if (frame.slice(start).startsWith('UkZC')) { - encoding = 'base64'; + this._encoding = 'base64'; } else { - encoding = 'binary'; + this._encoding = 'binary'; } - } else { - encoding = VNC_frame_encoding; } - if (iteration === 0) { - frame_length = VNC_frame_data.length; - test_state = 'running'; - } - - if (test_state !== 'running') { return; } - - iteration += 1; - if (iteration > iterations) { - finish(); - return; - } + this._rfb = undefined; + this._frame_length = this._frames.length; - frame_idx = 0; - istart_time = (new Date()).getTime(); - rfb.connect('test', 0, "bogus"); + this._frame_index = 0; + this._start_time = undefined; + this._realtime = true; + this._trafficManagement = true; - queue_next_packet(); + this._running = false; -}; + this.onfinish = function () {}; +} -end_iteration = function () { - if (rfb._display.pending()) { - rfb._display.set_onFlush(function () { - if (rfb._flushing) { - rfb._onFlush(); - } - end_iteration(); - }); - rfb._display.flush(); - } else { - next_iteration(); - } -}; +RecordingPlayer.prototype = { + run: function (realtime, trafficManagement) { + // initialize a new RFB + this._rfb = new RFB({'target': document.getElementById('VNC_canvas'), + 'view_only': true, + 'onDisconnected': this._handleDisconnect.bind(this), + 'onNotification': this._notification}); + this._enablePlaybackMode(); + + // reset the frame index and timer + this._frame_index = 0; + this._start_time = (new Date()).getTime(); + + this._realtime = realtime; + this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement; + + this._running = true; + + // launch the tests + this._rfb.connect('test', 0, 'bogus'); + + this._queueNextPacket(); + }, + + // _enablePlaybackMode mocks out things not required for running playback + _enablePlaybackMode: function () { + this._rfb._sock.send = function (arr) {}; + this._rfb._sock.close = function () {}; + this._rfb._sock.flush = function () {}; + this._rfb._checkEvents = function () {}; + this._rfb.connect = function (host, port, password, path) { + this._rfb_host = host; + this._rfb_port = port; + this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_path = (path !== undefined) ? path : ""; + this._sock.init('binary', 'ws'); + this._rfb_connection_state = 'connecting'; + this._rfb_init_state = 'ProtocolVersion'; + }; + }, + + _queueNextPacket: function () { + if (!this._running) { return; } + + var frame = this._frames[this._frame_index]; + + // skip send frames + while (this._frame_index < this._frame_length && frame.charAt(0) === "}") { + this._frame_index++; + frame = this._frames[this._frame_index]; + } -queue_next_packet = function () { - var frame, foffset, toffset, delay; - if (test_state !== 'running') { return; } + if (frame === 'EOF') { + Log.Debug('Finished, found EOF'); + this._finish(); + return; + } - frame = VNC_frame_data[frame_idx]; - while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) { - //Util.Debug("Send frame " + frame_idx); - frame_idx += 1; - frame = VNC_frame_data[frame_idx]; - } + if (this._frame_index >= this._frame_length) { + Log.Debug('Finished, no more frames'); + this._finish(); + return; + } - if (frame === 'EOF') { - Util.Debug("Finished, found EOF"); - end_iteration(); - return; - } - if (frame_idx >= frame_length) { - Util.Debug("Finished, no more frames"); - end_iteration(); - return; - } + if (this._realtime) { + let foffset = frame.slice(1, frame.indexOf('{', 1)); + let toffset = (new Date()).getTime() - this._start_time; + let delay = foffset - toffset; + if (delay < 1) delay = 1; - if (mode === 'realtime') { - foffset = frame.slice(1, frame.indexOf('{', 1)); - toffset = (new Date()).getTime() - istart_time; - delay = foffset - toffset; - if (delay < 1) { - delay = 1; + setTimeout(this._doPacket.bind(this), delay); + } else { + setImmediate(this._doPacket.bind(this)); + } + }, + + _doPacket: function () { + // Avoid having excessive queue buildup in non-realtime mode + if (!this._trafficManagement && this._rfb._flushing) { + let player = this; + this._rfb.display.set_onFlush(function () { + this._rfb._display.set_onFlush(this._rfb._onFlush.bind(this._rfb)); + this._rfb._onFlush(); + player._doPacket(); + }); + return; } - setTimeout(do_packet, delay); - } else { - window.setImmediate(do_packet); - } -}; - -var bytes_processed = 0; - -do_packet = function () { - // Avoid having an excessive queue buildup - if (rfb._flushing && (mode !== 'realtime')) { - rfb._display.set_onFlush(function () { - rfb._display.set_onFlush(rfb._onFlush.bind(rfb)); - rfb._onFlush(); - do_packet(); - }); - return; - } + const frame = this._frames[this._frame_index]; + var start = frame.indexOf('{', 1) + 1; + if (this._encoding === 'base64') { + var u8 = Base64.decode(frame.slice(start)); + start = 0; + } else { + var u8 = new Uint8Array(frame.length - start); + for (let i = 0; i < frame.length - start; i++) { + u8[i] = frame.charCodeAt(start + i); + } + } - //Util.Debug("Processing frame: " + frame_idx); - var frame = VNC_frame_data[frame_idx], - start = frame.indexOf('{', 1) + 1; - var u8; - if (encoding === 'base64') { - u8 = Base64.decode(frame.slice(start)); - start = 0; - } else { - u8 = new Uint8Array(frame.length - start); - for (var i = 0; i < frame.length - start; i++) { - u8[i] = frame.charCodeAt(start + i); + this._rfb._sock._recv_message({'data': u8}); + this._frame_index++; + + this._queueNextPacket(); + }, + + _finish() { + if (this._rfb._display.pending()) { + var player = this; + this._rfb._display.set_onFlush(function () { + if (player._rfb._flushing) { + player._rfb._onFlush(); + } + player._finish(); + }); + this._rfb._display.flush(); + } else { + this._running = false; + this.onfinish((new Date()).getTime() - this._start_time); } - } - bytes_processed += u8.length; - rfb._sock._recv_message({'data' : u8}); - frame_idx += 1; + }, - queue_next_packet(); + _handleDisconnect(rfb, reason) { + this._running = false; + this._disconnected(rfb, reason, this._frame_index); + } }; - diff --git a/tests/vnc_playback.html b/tests/vnc_playback.html index 65b735e..66f4057 100644 --- a/tests/vnc_playback.html +++ b/tests/vnc_playback.html @@ -2,6 +2,8 @@ VNC Playback + + @@ -9,8 +11,7 @@ Perftest:  Realtime:   -   +  

@@ -37,98 +38,5 @@ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'> --> - - - - - +