mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 21:27:40 +00:00
GUACAMOLE-377: Add JavaScript API support for tracking display render statistics.
This commit is contained in:
@@ -840,6 +840,12 @@ Guacamole.Client = function(tunnel) {
|
||||
* @event
|
||||
* @param {!number} timestamp
|
||||
* The timestamp associated with the sync instruction.
|
||||
*
|
||||
* @param {!number} frames
|
||||
* The number of frames that were considered or combined to produce the
|
||||
* frame associated with this sync instruction, or zero if this value
|
||||
* is not known or the remote desktop server provides no concept of
|
||||
* frames.
|
||||
*/
|
||||
this.onsync = null;
|
||||
|
||||
@@ -1530,6 +1536,7 @@ Guacamole.Client = function(tunnel) {
|
||||
"sync": function(parameters) {
|
||||
|
||||
var timestamp = parseInt(parameters[0]);
|
||||
var frames = parameters[1] ? parseInt(parameters[1]) : 0;
|
||||
|
||||
// Flush display, send sync when done
|
||||
display.flush(function displaySyncComplete() {
|
||||
@@ -1547,7 +1554,7 @@ Guacamole.Client = function(tunnel) {
|
||||
currentTimestamp = timestamp;
|
||||
}
|
||||
|
||||
});
|
||||
}, timestamp, frames);
|
||||
|
||||
// If received first update, no longer waiting.
|
||||
if (currentState === STATE_WAITING)
|
||||
@@ -1555,7 +1562,7 @@ Guacamole.Client = function(tunnel) {
|
||||
|
||||
// Call sync handler if defined
|
||||
if (guac_client.onsync)
|
||||
guac_client.onsync(timestamp);
|
||||
guac_client.onsync(timestamp, frames);
|
||||
|
||||
},
|
||||
|
||||
|
@@ -112,6 +112,17 @@ Guacamole.Display = function() {
|
||||
*/
|
||||
this.cursorY = 0;
|
||||
|
||||
/**
|
||||
* The number of milliseconds over which display rendering statistics
|
||||
* should be gathered, dispatching {@link #onstatistics} events as those
|
||||
* statistics are available. If set to zero, no statistics will be
|
||||
* gathered.
|
||||
*
|
||||
* @default 0
|
||||
* @type {!number}
|
||||
*/
|
||||
this.statisticWindow = 0;
|
||||
|
||||
/**
|
||||
* Fired when the default layer (and thus the entire Guacamole display)
|
||||
* is resized.
|
||||
@@ -142,6 +153,18 @@ Guacamole.Display = function() {
|
||||
*/
|
||||
this.oncursor = null;
|
||||
|
||||
/**
|
||||
* Fired whenever performance statistics are available for recently-
|
||||
* rendered frames. This event will fire only if {@link #statisticWindow}
|
||||
* is non-zero.
|
||||
*
|
||||
* @event
|
||||
* @param {!Guacamole.Display.Statistics} stats
|
||||
* An object containing general rendering performance statistics for
|
||||
* the remote desktop, Guacamole server, and Guacamole client.
|
||||
*/
|
||||
this.onstatistics = null;
|
||||
|
||||
/**
|
||||
* The queue of all pending Tasks. Tasks will be run in order, with new
|
||||
* tasks added at the end of the queue and old tasks removed from the
|
||||
@@ -186,6 +209,10 @@ Guacamole.Display = function() {
|
||||
*/
|
||||
var syncFlush = function syncFlush() {
|
||||
|
||||
var localTimestamp = 0;
|
||||
var remoteTimestamp = 0;
|
||||
|
||||
var renderedLogicalFrames = 0;
|
||||
var rendered_frames = 0;
|
||||
|
||||
// Draw all pending frames, if ready
|
||||
@@ -196,6 +223,10 @@ Guacamole.Display = function() {
|
||||
break;
|
||||
|
||||
frame.flush();
|
||||
|
||||
localTimestamp = frame.localTimestamp;
|
||||
remoteTimestamp = frame.remoteTimestamp;
|
||||
renderedLogicalFrames += frame.logicalFrames;
|
||||
rendered_frames++;
|
||||
|
||||
}
|
||||
@@ -203,6 +234,9 @@ Guacamole.Display = function() {
|
||||
// Remove rendered frames from array
|
||||
frames.splice(0, rendered_frames);
|
||||
|
||||
if (rendered_frames)
|
||||
notifyFlushed(localTimestamp, remoteTimestamp, renderedLogicalFrames);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -225,8 +259,11 @@ Guacamole.Display = function() {
|
||||
|
||||
// Flush the next frame only if it is ready (not awaiting
|
||||
// completion of some asynchronous operation like an image load)
|
||||
if (frames[0].isReady())
|
||||
frames.shift().flush();
|
||||
if (frames[0].isReady()) {
|
||||
var frame = frames.shift();
|
||||
frame.flush();
|
||||
notifyFlushed(frame.localTimestamp, frame.remoteTimestamp, frame.logicalFrames);
|
||||
}
|
||||
|
||||
// Request yet another animation frame if frames remain to be
|
||||
// flushed
|
||||
@@ -241,6 +278,101 @@ Guacamole.Display = function() {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Recently-gathered display render statistics, as made available by calls
|
||||
* to notifyFlushed(). The contents of this array will be trimmed to
|
||||
* contain only up to {@link #statisticWindow} milliseconds of statistics.
|
||||
*
|
||||
* @private
|
||||
* @type {Guacamole.Display.Statistics[]}
|
||||
*/
|
||||
var statistics = [];
|
||||
|
||||
/**
|
||||
* Notifies that one or more frames have been successfully rendered
|
||||
* (flushed) to the display.
|
||||
*
|
||||
* @private
|
||||
* @param {!number} localTimestamp
|
||||
* The local timestamp of the point in time at which the most recent,
|
||||
* flushed frame was received by the display, in milliseconds since the
|
||||
* Unix Epoch.
|
||||
*
|
||||
* @param {!number} remoteTimestamp
|
||||
* The remote timestamp of sync instruction associated with the most
|
||||
* recent, flushed frame received by the display. This timestamp is in
|
||||
* milliseconds, but is arbitrary, having meaning only relative to
|
||||
* other timestamps in the same connection.
|
||||
*
|
||||
* @param {!number} logicalFrames
|
||||
* The number of remote desktop frames that were flushed.
|
||||
*/
|
||||
var notifyFlushed = function notifyFlushed(localTimestamp, remoteTimestamp, logicalFrames) {
|
||||
|
||||
// Ignore if statistics are not being gathered
|
||||
if (!guac_display.statisticWindow)
|
||||
return;
|
||||
|
||||
var current = new Date().getTime();
|
||||
|
||||
// Find the first statistic that is still within the configured time
|
||||
// window
|
||||
for (var first = 0; first < statistics.length; first++) {
|
||||
if (current - statistics[first].timestamp <= guac_display.statisticWindow)
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove all statistics except those within the time window
|
||||
statistics.splice(0, first - 1);
|
||||
|
||||
// Record statistics for latest frame
|
||||
statistics.push({
|
||||
localTimestamp : localTimestamp,
|
||||
remoteTimestamp : remoteTimestamp,
|
||||
timestamp : current,
|
||||
frames : logicalFrames
|
||||
});
|
||||
|
||||
// Determine the actual time interval of the available statistics (this
|
||||
// will not perfectly match the configured interval, which is an upper
|
||||
// bound)
|
||||
var statDuration = (statistics[statistics.length - 1].timestamp - statistics[0].timestamp) / 1000;
|
||||
|
||||
// Determine the amount of time that elapsed remotely (within the
|
||||
// remote desktop)
|
||||
var remoteDuration = (statistics[statistics.length - 1].remoteTimestamp - statistics[0].remoteTimestamp) / 1000;
|
||||
|
||||
// Calculate the number of frames that have been rendered locally
|
||||
// within the configured time interval
|
||||
var localFrames = statistics.length;
|
||||
|
||||
// Calculate the number of frames actually received from the remote
|
||||
// desktop by the Guacamole server
|
||||
var remoteFrames = statistics.reduce(function sumFrames(prev, stat) {
|
||||
return prev + stat.frames;
|
||||
}, 0);
|
||||
|
||||
// Calculate the number of frames that the Guacamole server had to
|
||||
// drop or combine with other frames
|
||||
var drops = statistics.reduce(function sumDrops(prev, stat) {
|
||||
return prev + Math.max(0, stat.frames - 1);
|
||||
}, 0);
|
||||
|
||||
// Produce lag and FPS statistics from above raw measurements
|
||||
var stats = new Guacamole.Display.Statistics({
|
||||
processingLag : current - localTimestamp,
|
||||
desktopFps : (remoteDuration && remoteFrames) ? remoteFrames / remoteDuration : null,
|
||||
clientFps : statDuration ? localFrames / statDuration : null,
|
||||
serverFps : remoteDuration ? localFrames / remoteDuration : null,
|
||||
dropRate : remoteDuration ? drops / remoteDuration : null
|
||||
});
|
||||
|
||||
// Notify of availability of new statistics
|
||||
if (guac_display.onstatistics)
|
||||
guac_display.onstatistics(stats);
|
||||
|
||||
};
|
||||
|
||||
// Switch from asynchronous frame handling to synchronous frame handling if
|
||||
// requestAnimationFrame() is unlikely to be usable (browsers may not
|
||||
// invoke the animation frame callback if the relevant tab is not focused)
|
||||
@@ -281,8 +413,43 @@ Guacamole.Display = function() {
|
||||
*
|
||||
* @param {!Task[]} tasks
|
||||
* The set of tasks which must be executed to render this frame.
|
||||
*
|
||||
* @param {number} [timestamp]
|
||||
* The remote timestamp of sync instruction associated with this frame.
|
||||
* This timestamp is in milliseconds, but is arbitrary, having meaning
|
||||
* only relative to other remote timestamps in the same connection. If
|
||||
* omitted, a compatible but local timestamp will be used instead.
|
||||
*
|
||||
* @param {number} [logicalFrames=0]
|
||||
* The number of remote desktop frames that were combined to produce
|
||||
* this frame, or zero if this value is unknown or inapplicable.
|
||||
*/
|
||||
function Frame(callback, tasks) {
|
||||
var Frame = function Frame(callback, tasks, timestamp, logicalFrames) {
|
||||
|
||||
/**
|
||||
* The local timestamp of the point in time at which this frame was
|
||||
* received by the display, in milliseconds since the Unix Epoch.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.localTimestamp = new Date().getTime();
|
||||
|
||||
/**
|
||||
* The remote timestamp of sync instruction associated with this frame.
|
||||
* This timestamp is in milliseconds, but is arbitrary, having meaning
|
||||
* only relative to other remote timestamps in the same connection.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.remoteTimestamp = timestamp || this.localTimestamp;
|
||||
|
||||
/**
|
||||
* The number of remote desktop frames that were combined to produce
|
||||
* this frame. If unknown or not applicable, this will be zero.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.logicalFrames = logicalFrames || 0;
|
||||
|
||||
/**
|
||||
* Cancels rendering of this frame and all associated tasks. The
|
||||
@@ -337,7 +504,7 @@ Guacamole.Display = function() {
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A container for an task handler. Each operation which must be ordered
|
||||
@@ -514,11 +681,20 @@ Guacamole.Display = function() {
|
||||
* @param {function} [callback]
|
||||
* The function to call when this frame is flushed. This may happen
|
||||
* immediately, or later when blocked tasks become unblocked.
|
||||
*
|
||||
* @param {number} timestamp
|
||||
* The remote timestamp of sync instruction associated with this frame.
|
||||
* This timestamp is in milliseconds, but is arbitrary, having meaning
|
||||
* only relative to other remote timestamps in the same connection.
|
||||
*
|
||||
* @param {number} logicalFrames
|
||||
* The number of remote desktop frames that were combined to produce
|
||||
* this frame.
|
||||
*/
|
||||
this.flush = function(callback) {
|
||||
this.flush = function(callback, timestamp, logicalFrames) {
|
||||
|
||||
// Add frame, reset tasks
|
||||
frames.push(new Frame(callback, tasks));
|
||||
frames.push(new Frame(callback, tasks, timestamp, logicalFrames));
|
||||
tasks = [];
|
||||
|
||||
// Attempt flush
|
||||
@@ -1938,3 +2114,79 @@ Guacamole.Display.VisibleLayer = function(width, height) {
|
||||
* @type {!number}
|
||||
*/
|
||||
Guacamole.Display.VisibleLayer.__next_id = 0;
|
||||
|
||||
/**
|
||||
* A set of Guacamole display performance statistics, describing the speed at
|
||||
* which the remote desktop, Guacamole server, and Guacamole client are
|
||||
* rendering frames.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Guacamole.Display.Statistics|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* Guacamole.Display.Statistics.
|
||||
*/
|
||||
Guacamole.Display.Statistics = function Statistics(template) {
|
||||
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The amount of time that the Guacamole client is taking to render
|
||||
* individual frames, in milliseconds, if known. If this value is unknown,
|
||||
* such as if the there are insufficient frame statistics recorded to
|
||||
* calculate this value, this will be null.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
this.processingLag = template.processingLag;
|
||||
|
||||
/**
|
||||
* The framerate of the remote desktop currently being viewed within the
|
||||
* relevant Gucamole.Display, independent of Guacamole, in frames per
|
||||
* second. This represents the speed at which the remote desktop is
|
||||
* producing frame data for the Guacamole server to consume. If this
|
||||
* value is unknown, such as if the remote desktop server does not actually
|
||||
* define frame boundaries, this will be null.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
this.desktopFps = template.desktopFps;
|
||||
|
||||
/**
|
||||
* The rate at which the Guacamole server is generating frames for the
|
||||
* Guacamole client to consume, in frames per second. If the Guacamole
|
||||
* server is correctly adjusting for variance in client/browser processing
|
||||
* power, this rate should closely match the client rate, and should remain
|
||||
* independent of any network latency. If this value is unknown, such as if
|
||||
* the there are insufficient frame statistics recorded to calculate this
|
||||
* value, this will be null.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
this.serverFps = template.serverFps;
|
||||
|
||||
/**
|
||||
* The rate at which the Guacamole client is consuming frames generated by
|
||||
* the Guacamole server, in frames per second. If the Guacamole server is
|
||||
* correctly adjusting for variance in client/browser processing power,
|
||||
* this rate should closely match the server rate, regardless of any
|
||||
* latency on the network between the server and client. If this value is
|
||||
* unknown, such as if the there are insufficient frame statistics recorded
|
||||
* to calculate this value, this will be null.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
this.clientFps = template.clientFps;
|
||||
|
||||
/**
|
||||
* The rate at which the Guacamole server is dropping or combining frames
|
||||
* received from the remote desktop server to compensate for variance in
|
||||
* client/browser processing power, in frames per second. This value may
|
||||
* also be non-zero if the server is compensating for variances in its own
|
||||
* processing power, or relative slowness in image compression vs. the rate
|
||||
* that inbound frames are received. If this value is unknown, such as if
|
||||
* the remote desktop server does not actually define frame boundaries,
|
||||
* this will be null.
|
||||
*/
|
||||
this.dropRate = template.dropRate;
|
||||
|
||||
};
|
||||
|
Reference in New Issue
Block a user