mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-377: Merge client-side support for frame tracking, RemoteFX parameter, and performance tracking extension.
This commit is contained in:
1
extensions/guacamole-display-statistics/.gitignore
vendored
Normal file
1
extensions/guacamole-display-statistics/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/main/resources/generated/
|
1
extensions/guacamole-display-statistics/.ratignore
Normal file
1
extensions/guacamole-display-statistics/.ratignore
Normal file
@@ -0,0 +1 @@
|
||||
src/main/resources/html/*.html
|
124
extensions/guacamole-display-statistics/pom.xml
Normal file
124
extensions/guacamole-display-statistics/pom.xml
Normal 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>
|
@@ -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>
|
@@ -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;
|
||||
|
||||
}]);
|
@@ -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"
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<meta name="after" content=".client-tile guac-client">
|
||||
|
||||
<!-- Guacamole display statistics -->
|
||||
<guac-client-statistics client="client"></guac-client-statistics>
|
@@ -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.
|
||||
*/
|
@@ -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: '-';
|
||||
}
|
@@ -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>
|
@@ -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"
|
||||
|
||||
}
|
||||
}
|
@@ -53,6 +53,9 @@
|
||||
<module>guacamole-history-recording-storage</module>
|
||||
<module>guacamole-vault</module>
|
||||
|
||||
<!-- Utility extensions -->
|
||||
<module>guacamole-display-statistics</module>
|
||||
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
@@ -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
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
@@ -298,6 +298,11 @@
|
||||
"name" : "disable-glyph-caching",
|
||||
"type" : "BOOLEAN",
|
||||
"options" : [ "true" ]
|
||||
},
|
||||
{
|
||||
"name" : "disable-gfx",
|
||||
"type" : "BOOLEAN",
|
||||
"options" : [ "true" ]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -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:",
|
||||
|
Reference in New Issue
Block a user