GUACAMOLE-377: Merge client-side support for frame tracking, RemoteFX parameter, and performance tracking extension.

This commit is contained in:
James Muehlner
2022-06-13 12:58:50 -07:00
committed by GitHub
16 changed files with 804 additions and 8 deletions

View File

@@ -0,0 +1 @@
src/main/resources/generated/

View File

@@ -0,0 +1 @@
src/main/resources/html/*.html

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-display-statistics</artifactId>
<packaging>jar</packaging>
<version>1.4.0</version>
<name>guacamole-display-statistics</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>extensions</artifactId>
<version>1.4.0</version>
<relativePath>../</relativePath>
</parent>
<build>
<plugins>
<!-- Pre-cache Angular templates with maven-angular-plugin -->
<plugin>
<groupId>com.keithbranton.mojo</groupId>
<artifactId>angular-maven-plugin</artifactId>
<version>0.3.4</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>html2js</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDir>${basedir}/src/main/resources</sourceDir>
<include>**/*.html</include>
<target>${basedir}/src/main/resources/generated/templates-main/templates.js</target>
<prefix>app/ext/display-stats</prefix>
</configuration>
</plugin>
<!-- JS/CSS Minification Plugin -->
<plugin>
<groupId>com.github.buckelieg</groupId>
<artifactId>minify-maven-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<charset>UTF-8</charset>
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
<cssSourceDir>/</cssSourceDir>
<cssTargetDir>/</cssTargetDir>
<cssFinalFile>display-stats.css</cssFinalFile>
<cssSourceFiles>
<cssSourceFile>license.txt</cssSourceFile>
</cssSourceFiles>
<cssSourceIncludes>
<cssSourceInclude>**/*.css</cssSourceInclude>
</cssSourceIncludes>
<jsSourceDir>/</jsSourceDir>
<jsTargetDir>/</jsTargetDir>
<jsFinalFile>display-stats.js</jsFinalFile>
<jsSourceFiles>
<jsSourceFile>license.txt</jsSourceFile>
</jsSourceFiles>
<jsSourceIncludes>
<jsSourceInclude>**/*.js</jsSourceInclude>
</jsSourceIncludes>
<!-- Do not minify and include tests -->
<jsSourceExcludes>
<jsSourceExclude>**/*.test.js</jsSourceExclude>
</jsSourceExcludes>
<jsEngine>CLOSURE</jsEngine>
<!-- Disable warnings for JSDoc annotations -->
<closureWarningLevels>
<misplacedTypeAnnotation>OFF</misplacedTypeAnnotation>
<nonStandardJsDocs>OFF</nonStandardJsDocs>
</closureWarningLevels>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
<!-- Output tar.gz -->
<formats>
<format>tar.gz</format>
</formats>
<!-- Include licenses and extension .jar -->
<fileSets>
<!-- Include licenses -->
<fileSet>
<outputDirectory></outputDirectory>
<directory>target/licenses</directory>
</fileSet>
<!-- Include extension .jar -->
<fileSet>
<directory>target</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,108 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A directive which displays frame rendering performance statistics for a
* Guacamole client.
*/
angular.module('client').directive('guacClientStatistics', [function guacClientStatistics() {
const directive = {
restrict: 'E',
templateUrl: 'app/ext/display-stats/templates/guacClientStatistics.html',
};
directive.scope = {
/**
* The Guacamole client to display frame rendering statistics for.
*
* @type ManagedClient
*/
client : '='
};
directive.controller = ['$scope', function guacClientStatisticsController($scope) {
/**
* Updates the displayed frame rendering statistics to the values
* within the given statistics object.
*
* @param {!Guacamole.Display.Statistics} stats
* An object containing general rendering performance statistics for
* the remote desktop, Guacamole server, and Guacamole client.
*/
var updateStatistics = function updateStatistics(stats) {
$scope.$apply(function statisticsChanged() {
$scope.statistics = stats;
});
};
/**
* Returns whether the given value is a defined value that should be
* rendered within the statistics toolbar.
*
* @param {number} value
* The value to test.
*
* @returns {!boolean}
* true if the given value should be rendered within the statistics
* toolbar, false otherwise.
*/
$scope.hasValue = function hasValue(value) {
return value || value === 0;
};
/**
* Rounds the given numeric value to the nearest hundredth (two decimal places).
*
* @param {!number} value
* The value to round.
*
* @param {!number}
* The given value, rounded to the nearest hundredth.
*/
$scope.round = function round(value) {
return Math.round(value * 100) / 100;
};
// Assign/remove onstatistics handlers to track the statistics of the
// current client
$scope.$watch('client', function clientChanged(client, oldClient) {
if (oldClient)
oldClient.managedDisplay.display.onstatistics = null;
client.managedDisplay.display.statisticWindow = 1000;
client.managedDisplay.display.onstatistics = updateStatistics;
});
// Clear onstatistics handler when directive is being unloaded
$scope.$on('$destroy', function scopeDestroyed() {
if ($scope.client)
$scope.client.managedDisplay.display.onstatistics = null;
});
}];
return directive;
}]);

View File

@@ -0,0 +1,28 @@
{
"guacamoleVersion" : "1.4.0",
"name" : "Display Statistic Toolbar",
"namespace" : "display-stats",
"translations" : [
"translations/en.json"
],
"js" : [
"display-stats.min.js"
],
"css" : [
"display-stats.min.css"
],
"html" : [
"html/add-statistics.html"
],
"resources" : {
"templates/guacClientStatistics.html" : "text/html"
}
}

View File

@@ -0,0 +1,4 @@
<meta name="after" content=".client-tile guac-client">
<!-- Guacamole display statistics -->
<guac-client-statistics client="client"></guac-client-statistics>

View File

@@ -0,0 +1,18 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

View File

@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
guac-client-statistics {
font-size: 13px;
color: white;
background: #111;
}
guac-client-statistics dl.client-statistics {
display: table;
margin: 0;
padding: 0.25em;
}
guac-client-statistics dl.client-statistics dt,
guac-client-statistics dl.client-statistics dd {
display: table-cell;
padding: 0.25em;
}
guac-client-statistics dl.client-statistics dt {
padding-right: 0.5em;
padding-left: 1em;
}
guac-client-statistics dl.client-statistics dt:first-child {
padding-left: 0.5em;
}
guac-client-statistics dl.client-statistics dd {
min-width: 6em;
border: 1px solid rgba(255, 255, 255, 0.125);
border-radius: 3px;
background: black;
}
guac-client-statistics dl.client-statistics dd.no-value::before {
color: #888;
content: '-';
}

View File

@@ -0,0 +1,39 @@
<dl class="client-statistics">
<dt class="client-statistic desktop-fps">
{{ 'CLIENT.FIELD_HEADER_DESKTOP_FRAMERATE' | translate }}
</dt>
<dd ng-class="{ 'no-value' : !hasValue(statistics.desktopFps) }">
<span ng-show="hasValue(statistics.desktopFps)"
translate="CLIENT.INFO_FRAMERATE"
translate-values="{ VALUE : round(statistics.desktopFps) }"></span>
</dd>
<dt class="client-statistic server-fps">
{{ 'CLIENT.FIELD_HEADER_SERVER_FRAMERATE' | translate }}
</dt>
<dd ng-class="{ 'no-value' : !hasValue(statistics.serverFps) }">
<span ng-show="hasValue(statistics.serverFps)"
translate="CLIENT.INFO_FRAMERATE"
translate-values="{ VALUE : round(statistics.serverFps) }"></span>
</dd>
<dt class="client-statistic client-fps">
{{ 'CLIENT.FIELD_HEADER_CLIENT_FRAMERATE' | translate }}
</dt>
<dd ng-class="{ 'no-value' : !hasValue(statistics.clientFps) }">
<span ng-show="hasValue(statistics.clientFps)"
translate="CLIENT.INFO_FRAMERATE"
translate-values="{ VALUE : round(statistics.clientFps) }"></span>
</dd>
<dt class="client-statistic drop-rate">
{{ 'CLIENT.FIELD_HEADER_DROP_FRAMERATE' | translate }}
</dt>
<dd ng-class="{ 'no-value' : !hasValue(statistics.dropRate) }">
<span ng-show="hasValue(statistics.dropRate)"
translate="CLIENT.INFO_FRAMERATE"
translate-values="{ VALUE : round(statistics.dropRate) }"></span>
</dd>
</dl>

View File

@@ -0,0 +1,12 @@
{
"CLIENT" : {
"FIELD_HEADER_CLIENT_FRAMERATE" : "Guacamole (Client):",
"FIELD_HEADER_DESKTOP_FRAMERATE" : "Remote Desktop:",
"FIELD_HEADER_DROP_FRAMERATE" : "Drop:",
"FIELD_HEADER_SERVER_FRAMERATE" : "Guacamole (Server):",
"INFO_FRAMERATE" : "{VALUE} fps"
}
}

View File

@@ -53,6 +53,9 @@
<module>guacamole-history-recording-storage</module> <module>guacamole-history-recording-storage</module>
<module>guacamole-vault</module> <module>guacamole-vault</module>
<!-- Utility extensions -->
<module>guacamole-display-statistics</module>
</modules> </modules>
<build> <build>

View File

@@ -840,6 +840,12 @@ Guacamole.Client = function(tunnel) {
* @event * @event
* @param {!number} timestamp * @param {!number} timestamp
* The timestamp associated with the sync instruction. * 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; this.onsync = null;
@@ -1530,6 +1536,7 @@ Guacamole.Client = function(tunnel) {
"sync": function(parameters) { "sync": function(parameters) {
var timestamp = parseInt(parameters[0]); var timestamp = parseInt(parameters[0]);
var frames = parameters[1] ? parseInt(parameters[1]) : 0;
// Flush display, send sync when done // Flush display, send sync when done
display.flush(function displaySyncComplete() { display.flush(function displaySyncComplete() {
@@ -1547,7 +1554,7 @@ Guacamole.Client = function(tunnel) {
currentTimestamp = timestamp; currentTimestamp = timestamp;
} }
}); }, timestamp, frames);
// If received first update, no longer waiting. // If received first update, no longer waiting.
if (currentState === STATE_WAITING) if (currentState === STATE_WAITING)
@@ -1555,7 +1562,7 @@ Guacamole.Client = function(tunnel) {
// Call sync handler if defined // Call sync handler if defined
if (guac_client.onsync) if (guac_client.onsync)
guac_client.onsync(timestamp); guac_client.onsync(timestamp, frames);
}, },

View File

@@ -112,6 +112,17 @@ Guacamole.Display = function() {
*/ */
this.cursorY = 0; 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) * Fired when the default layer (and thus the entire Guacamole display)
* is resized. * is resized.
@@ -142,6 +153,18 @@ Guacamole.Display = function() {
*/ */
this.oncursor = null; 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 * 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 * tasks added at the end of the queue and old tasks removed from the
@@ -163,11 +186,33 @@ Guacamole.Display = function() {
var frames = []; var frames = [];
/** /**
* Flushes all pending frames. * The ID of the animation frame request returned by the last call to
* requestAnimationFrame(). This value will only be set if the browser
* supports requestAnimationFrame(), if a frame render is currently
* pending, and if the current browser tab is currently focused (likely to
* handle requests for animation frames). In all other cases, this will be
* null.
*
* @private
* @type {number}
*/
var inProgressFrame = null;
/**
* Flushes all pending frames synchronously. This function will block until
* all pending frames have rendered. If a frame is currently blocked by an
* asynchronous operation like an image load, this function will return
* after reaching that operation and the flush operation will
* automamtically resume after that operation completes.
*
* @private * @private
*/ */
function __flush_frames() { var syncFlush = function syncFlush() {
var localTimestamp = 0;
var remoteTimestamp = 0;
var renderedLogicalFrames = 0;
var rendered_frames = 0; var rendered_frames = 0;
// Draw all pending frames, if ready // Draw all pending frames, if ready
@@ -178,6 +223,10 @@ Guacamole.Display = function() {
break; break;
frame.flush(); frame.flush();
localTimestamp = frame.localTimestamp;
remoteTimestamp = frame.remoteTimestamp;
renderedLogicalFrames += frame.logicalFrames;
rendered_frames++; rendered_frames++;
} }
@@ -185,6 +234,172 @@ Guacamole.Display = function() {
// Remove rendered frames from array // Remove rendered frames from array
frames.splice(0, rendered_frames); frames.splice(0, rendered_frames);
if (rendered_frames)
notifyFlushed(localTimestamp, remoteTimestamp, renderedLogicalFrames);
};
/**
* Flushes all pending frames asynchronously. This function returns
* immediately, relying on requestAnimationFrame() to dictate when each
* frame should be flushed.
*
* @private
*/
var asyncFlush = function asyncFlush() {
var continueFlush = function continueFlush() {
// We're no longer waiting to render a frame
inProgressFrame = null;
// Nothing to do if there are no frames remaining
if (!frames.length)
return;
// Flush the next frame only if it is ready (not awaiting
// completion of some asynchronous operation like an image load)
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
if (frames.length)
inProgressFrame = window.requestAnimationFrame(continueFlush);
};
// Begin flushing frames if not already waiting to render a frame
if (!inProgressFrame)
inProgressFrame = window.requestAnimationFrame(continueFlush);
};
/**
* 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)
window.addEventListener('blur', function switchToSyncFlush() {
if (inProgressFrame && !document.hasFocus()) {
// Cancel pending asynchronous processing of frame ...
window.cancelAnimationFrame(inProgressFrame);
inProgressFrame = null;
// ... and instead process it synchronously
syncFlush();
}
}, true);
/**
* Flushes all pending frames.
* @private
*/
function __flush_frames() {
if (window.requestAnimationFrame && document.hasFocus())
asyncFlush();
else
syncFlush();
} }
/** /**
@@ -198,8 +413,43 @@ Guacamole.Display = function() {
* *
* @param {!Task[]} tasks * @param {!Task[]} tasks
* The set of tasks which must be executed to render this frame. * 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 * Cancels rendering of this frame and all associated tasks. The
@@ -254,7 +504,7 @@ Guacamole.Display = function() {
}; };
} };
/** /**
* A container for an task handler. Each operation which must be ordered * A container for an task handler. Each operation which must be ordered
@@ -431,11 +681,20 @@ Guacamole.Display = function() {
* @param {function} [callback] * @param {function} [callback]
* The function to call when this frame is flushed. This may happen * The function to call when this frame is flushed. This may happen
* immediately, or later when blocked tasks become unblocked. * 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 // Add frame, reset tasks
frames.push(new Frame(callback, tasks)); frames.push(new Frame(callback, tasks, timestamp, logicalFrames));
tasks = []; tasks = [];
// Attempt flush // Attempt flush
@@ -1855,3 +2114,79 @@ Guacamole.Display.VisibleLayer = function(width, height) {
* @type {!number} * @type {!number}
*/ */
Guacamole.Display.VisibleLayer.__next_id = 0; 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;
};

View File

@@ -298,6 +298,11 @@
"name" : "disable-glyph-caching", "name" : "disable-glyph-caching",
"type" : "BOOLEAN", "type" : "BOOLEAN",
"options" : [ "true" ] "options" : [ "true" ]
},
{
"name" : "disable-gfx",
"type" : "BOOLEAN",
"options" : [ "true" ]
} }
] ]
}, },

View File

@@ -494,6 +494,7 @@
"FIELD_HEADER_DISABLE_BITMAP_CACHING" : "Disable bitmap caching:", "FIELD_HEADER_DISABLE_BITMAP_CACHING" : "Disable bitmap caching:",
"FIELD_HEADER_DISABLE_OFFSCREEN_CACHING" : "Disable off-screen caching:", "FIELD_HEADER_DISABLE_OFFSCREEN_CACHING" : "Disable off-screen caching:",
"FIELD_HEADER_DISABLE_GLYPH_CACHING" : "Disable glyph caching:", "FIELD_HEADER_DISABLE_GLYPH_CACHING" : "Disable glyph caching:",
"FIELD_HEADER_DISABLE_GFX" : "Disable Graphics Pipeline Extension:",
"FIELD_HEADER_ENABLE_PRINTING" : "Enable printing:", "FIELD_HEADER_ENABLE_PRINTING" : "Enable printing:",
"FIELD_HEADER_ENABLE_SFTP" : "Enable SFTP:", "FIELD_HEADER_ENABLE_SFTP" : "Enable SFTP:",
"FIELD_HEADER_ENABLE_THEMING" : "Enable theming:", "FIELD_HEADER_ENABLE_THEMING" : "Enable theming:",