diff --git a/extensions/guacamole-display-statistics/.gitignore b/extensions/guacamole-display-statistics/.gitignore
new file mode 100644
index 000000000..e55f47f1f
--- /dev/null
+++ b/extensions/guacamole-display-statistics/.gitignore
@@ -0,0 +1 @@
+src/main/resources/generated/
diff --git a/extensions/guacamole-display-statistics/.ratignore b/extensions/guacamole-display-statistics/.ratignore
new file mode 100644
index 000000000..da318d12f
--- /dev/null
+++ b/extensions/guacamole-display-statistics/.ratignore
@@ -0,0 +1 @@
+src/main/resources/html/*.html
diff --git a/extensions/guacamole-display-statistics/pom.xml b/extensions/guacamole-display-statistics/pom.xml
new file mode 100644
index 000000000..403d974cf
--- /dev/null
+++ b/extensions/guacamole-display-statistics/pom.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ 4.0.0
+ org.apache.guacamole
+ guacamole-display-statistics
+ jar
+ 1.4.0
+ guacamole-display-statistics
+ http://guacamole.apache.org/
+
+
+ org.apache.guacamole
+ extensions
+ 1.4.0
+ ../
+
+
+
+
+
+
+
+ com.keithbranton.mojo
+ angular-maven-plugin
+ 0.3.4
+
+
+ generate-resources
+
+ html2js
+
+
+
+
+ ${basedir}/src/main/resources
+ **/*.html
+ ${basedir}/src/main/resources/generated/templates-main/templates.js
+ app/ext/display-stats
+
+
+
+
+
+ com.github.buckelieg
+ minify-maven-plugin
+
+
+ default-cli
+
+ UTF-8
+
+ ${basedir}/src/main/resources
+ ${project.build.directory}/classes
+
+ /
+ /
+ display-stats.css
+
+
+ license.txt
+
+
+
+ **/*.css
+
+
+ /
+ /
+ display-stats.js
+
+
+ license.txt
+
+
+
+ **/*.js
+
+
+
+
+ **/*.test.js
+
+ CLOSURE
+
+
+
+ OFF
+ OFF
+
+
+
+
+ minify
+
+
+
+
+
+
+
+
+
diff --git a/extensions/guacamole-display-statistics/src/main/assembly/dist.xml b/extensions/guacamole-display-statistics/src/main/assembly/dist.xml
new file mode 100644
index 000000000..0b16a7147
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/assembly/dist.xml
@@ -0,0 +1,53 @@
+
+
+
+
+ dist
+ ${project.artifactId}-${project.version}
+
+
+
+ tar.gz
+
+
+
+
+
+
+
+
+ target/licenses
+
+
+
+
+ target
+
+
+ *.jar
+
+
+
+
+
+
diff --git a/extensions/guacamole-display-statistics/src/main/resources/directives/guacClientStatistics.js b/extensions/guacamole-display-statistics/src/main/resources/directives/guacClientStatistics.js
new file mode 100644
index 000000000..4dc6b8a15
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/directives/guacClientStatistics.js
@@ -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;
+
+}]);
diff --git a/extensions/guacamole-display-statistics/src/main/resources/guac-manifest.json b/extensions/guacamole-display-statistics/src/main/resources/guac-manifest.json
new file mode 100644
index 000000000..71c84b384
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/guac-manifest.json
@@ -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"
+ }
+
+}
diff --git a/extensions/guacamole-display-statistics/src/main/resources/html/add-statistics.html b/extensions/guacamole-display-statistics/src/main/resources/html/add-statistics.html
new file mode 100644
index 000000000..5978f7f58
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/html/add-statistics.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/extensions/guacamole-display-statistics/src/main/resources/license.txt b/extensions/guacamole-display-statistics/src/main/resources/license.txt
new file mode 100644
index 000000000..042f3ce1f
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/license.txt
@@ -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.
+ */
diff --git a/extensions/guacamole-display-statistics/src/main/resources/styles/clientStatistics.css b/extensions/guacamole-display-statistics/src/main/resources/styles/clientStatistics.css
new file mode 100644
index 000000000..ba4661469
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/styles/clientStatistics.css
@@ -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: '-';
+}
diff --git a/extensions/guacamole-display-statistics/src/main/resources/templates/guacClientStatistics.html b/extensions/guacamole-display-statistics/src/main/resources/templates/guacClientStatistics.html
new file mode 100644
index 000000000..ae7537c0f
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/templates/guacClientStatistics.html
@@ -0,0 +1,39 @@
+
+
+ -
+ {{ 'CLIENT.FIELD_HEADER_DESKTOP_FRAMERATE' | translate }}
+
+ -
+
+
+
+ -
+ {{ 'CLIENT.FIELD_HEADER_SERVER_FRAMERATE' | translate }}
+
+ -
+
+
+
+ -
+ {{ 'CLIENT.FIELD_HEADER_CLIENT_FRAMERATE' | translate }}
+
+ -
+
+
+
+ -
+ {{ 'CLIENT.FIELD_HEADER_DROP_FRAMERATE' | translate }}
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/extensions/guacamole-display-statistics/src/main/resources/translations/en.json b/extensions/guacamole-display-statistics/src/main/resources/translations/en.json
new file mode 100644
index 000000000..ef42908e4
--- /dev/null
+++ b/extensions/guacamole-display-statistics/src/main/resources/translations/en.json
@@ -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"
+
+ }
+}
diff --git a/extensions/pom.xml b/extensions/pom.xml
index de2b24556..3bab33257 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -53,6 +53,9 @@
guacamole-history-recording-storage
guacamole-vault
+
+ guacamole-display-statistics
+
diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js
index 83d89abaa..2cc9c28b1 100644
--- a/guacamole-common-js/src/main/webapp/modules/Client.js
+++ b/guacamole-common-js/src/main/webapp/modules/Client.js
@@ -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);
},
diff --git a/guacamole-common-js/src/main/webapp/modules/Display.js b/guacamole-common-js/src/main/webapp/modules/Display.js
index 8baa6b7d7..374a7620d 100644
--- a/guacamole-common-js/src/main/webapp/modules/Display.js
+++ b/guacamole-common-js/src/main/webapp/modules/Display.js
@@ -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
@@ -163,11 +186,33 @@ Guacamole.Display = function() {
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
*/
- function __flush_frames() {
+ var syncFlush = function syncFlush() {
+ var localTimestamp = 0;
+ var remoteTimestamp = 0;
+
+ var renderedLogicalFrames = 0;
var rendered_frames = 0;
// Draw all pending frames, if ready
@@ -178,6 +223,10 @@ Guacamole.Display = function() {
break;
frame.flush();
+
+ localTimestamp = frame.localTimestamp;
+ remoteTimestamp = frame.remoteTimestamp;
+ renderedLogicalFrames += frame.logicalFrames;
rendered_frames++;
}
@@ -185,6 +234,172 @@ Guacamole.Display = function() {
// Remove rendered frames from array
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
* 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
@@ -254,7 +504,7 @@ Guacamole.Display = function() {
};
- }
+ };
/**
* A container for an task handler. Each operation which must be ordered
@@ -431,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
@@ -1855,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;
+
+};
diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json
index e78313d79..c51f76209 100644
--- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json
+++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json
@@ -298,6 +298,11 @@
"name" : "disable-glyph-caching",
"type" : "BOOLEAN",
"options" : [ "true" ]
+ },
+ {
+ "name" : "disable-gfx",
+ "type" : "BOOLEAN",
+ "options" : [ "true" ]
}
]
},
diff --git a/guacamole/src/main/frontend/src/translations/en.json b/guacamole/src/main/frontend/src/translations/en.json
index db502311d..05a9fa279 100644
--- a/guacamole/src/main/frontend/src/translations/en.json
+++ b/guacamole/src/main/frontend/src/translations/en.json
@@ -494,6 +494,7 @@
"FIELD_HEADER_DISABLE_BITMAP_CACHING" : "Disable bitmap caching:",
"FIELD_HEADER_DISABLE_OFFSCREEN_CACHING" : "Disable off-screen 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_SFTP" : "Enable SFTP:",
"FIELD_HEADER_ENABLE_THEMING" : "Enable theming:",