287 lines
9.1 KiB
JavaScript
287 lines
9.1 KiB
JavaScript
|
/**
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; under version 2
|
||
|
* of the License (non-upgradable).
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
*
|
||
|
* Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
|
||
|
*/
|
||
|
define([
|
||
|
'jquery',
|
||
|
'lodash',
|
||
|
'i18n',
|
||
|
'async',
|
||
|
'context',
|
||
|
'helpers',
|
||
|
'taoClientDiagnostic/tools/stats',
|
||
|
'taoQtiItem/qtiItem/core/Loader',
|
||
|
'taoQtiItem/qtiCommonRenderer/renderers/Renderer',
|
||
|
'taoClientDiagnostic/tools/getConfig',
|
||
|
'taoClientDiagnostic/tools/getLabels',
|
||
|
'taoClientDiagnostic/tools/getStatus'
|
||
|
], function($, _, __, async, context, helpers, stats, Loader, Renderer, getConfig, getLabels, getStatus) {
|
||
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Duration of one second (in milliseconds)
|
||
|
* @type {number}
|
||
|
* @private
|
||
|
*/
|
||
|
const _second = 1000;
|
||
|
|
||
|
/**
|
||
|
* Default timeout duration
|
||
|
* @type {number}
|
||
|
* @private
|
||
|
*/
|
||
|
const _defaultTimeout = 30 * _second;
|
||
|
|
||
|
/**
|
||
|
* Default number of renderings by samples
|
||
|
* @type {number}
|
||
|
* @private
|
||
|
*/
|
||
|
const _defaultOccurrencesCount = 10;
|
||
|
|
||
|
/**
|
||
|
* List of default samples
|
||
|
* @type {Array}
|
||
|
* @private
|
||
|
*/
|
||
|
const _defaultSamples = [
|
||
|
'taoClientDiagnostic/tools/performances/data/sample1/',
|
||
|
'taoClientDiagnostic/tools/performances/data/sample2/',
|
||
|
'taoClientDiagnostic/tools/performances/data/sample3/'
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* Default values for the performances tester
|
||
|
* @type {object}
|
||
|
* @private
|
||
|
*/
|
||
|
const _defaults = {
|
||
|
id: 'performances',
|
||
|
|
||
|
// The threshold for optimal performances
|
||
|
optimal: 0.025,
|
||
|
|
||
|
// The threshold for minimal performances
|
||
|
threshold: 0.25
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* A list of thresholds for performances check
|
||
|
* @type {Array}
|
||
|
* @private
|
||
|
*/
|
||
|
const _thresholds = [
|
||
|
{
|
||
|
threshold: 0,
|
||
|
message: __('Very slow performances'),
|
||
|
type: 'error'
|
||
|
},
|
||
|
{
|
||
|
threshold: 33,
|
||
|
message: __('Average performances'),
|
||
|
type: 'warning'
|
||
|
},
|
||
|
{
|
||
|
threshold: 66,
|
||
|
message: __('Good performances'),
|
||
|
type: 'success'
|
||
|
}
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* List of translated texts per level.
|
||
|
* The level is provided through the config as a numeric value, starting from 1.
|
||
|
* @type {object}
|
||
|
* @private
|
||
|
*/
|
||
|
const _messages = [
|
||
|
// level 1
|
||
|
{
|
||
|
title: __('Workstation performances'),
|
||
|
status: __('Checking the performances...'),
|
||
|
performancesMin: __('Minimum rendering time'),
|
||
|
performancesMax: __('Maximum rendering time'),
|
||
|
performancesAverage: __('Average rendering time')
|
||
|
}
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* Base text used to build sample identifiers
|
||
|
* @type {string}
|
||
|
* @private
|
||
|
*/
|
||
|
const _sampleBaseId = 'sample';
|
||
|
|
||
|
/**
|
||
|
* Loads a page inside a div and compute the time to load
|
||
|
* @param {object} data The descriptor of the page to load
|
||
|
* @param {Function} done A callback function called to provide the result
|
||
|
* @private
|
||
|
*/
|
||
|
function loadItem(data, done) {
|
||
|
//item location config
|
||
|
const qtiJsonFile = `${data.url}qti.json`;
|
||
|
const urlTokens = data.url.split('/');
|
||
|
const extension = urlTokens[0];
|
||
|
const fullpath = require.s.contexts._.config.paths[extension];
|
||
|
const baseUrl = data.url.replace(extension, fullpath);
|
||
|
const loader = new Loader();
|
||
|
const renderer = new Renderer({
|
||
|
baseUrl: baseUrl // compatibility mode for legacy installations
|
||
|
});
|
||
|
|
||
|
// check needed by compatibility mode for legacy installations
|
||
|
if (renderer.getAssetManager) {
|
||
|
renderer.getAssetManager().setData('baseUrl', baseUrl);
|
||
|
}
|
||
|
|
||
|
require([`json!${qtiJsonFile}`], function(itemData) {
|
||
|
loader.loadItemData(itemData, function(item) {
|
||
|
renderer.load(function() {
|
||
|
//start right before rendering
|
||
|
const start = window.performance.now();
|
||
|
|
||
|
//set renderer
|
||
|
item.setRenderer(this);
|
||
|
|
||
|
//render markup
|
||
|
const $container = $('<div>').appendTo('body');
|
||
|
$container.append(item.render());
|
||
|
|
||
|
//execute javascript
|
||
|
item.postRender();
|
||
|
|
||
|
//remove item
|
||
|
$container.remove();
|
||
|
|
||
|
//done
|
||
|
const end = window.performance.now();
|
||
|
|
||
|
const duration = (end - start) / _second;
|
||
|
|
||
|
const result = {
|
||
|
id: data.id,
|
||
|
url: data.url,
|
||
|
duration: duration
|
||
|
};
|
||
|
|
||
|
done(null, result);
|
||
|
}, this.getLoadedClasses());
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Performs a browser performances test by running a heavy page
|
||
|
*
|
||
|
* @param {object} config - Some optional configs
|
||
|
* @param {string} [config.id] - The identifier of the test
|
||
|
* @param {number} [config.optimal] - The threshold for optimal performances
|
||
|
* @param {number} [config.threshold] - The threshold for minimal performances
|
||
|
* @param {string} [config.level] - The intensity level of the test. It will aim which messages list to use.
|
||
|
* @returns {object}
|
||
|
*/
|
||
|
return function performancesTester(config) {
|
||
|
const initConfig = getConfig(config, _defaults);
|
||
|
const labels = getLabels(_messages, initConfig.level);
|
||
|
let idx = 0;
|
||
|
const _samples = _.map((!_.isEmpty(initConfig.samples) && initConfig.samples) || _defaultSamples, sample => {
|
||
|
idx++;
|
||
|
return {
|
||
|
id: _sampleBaseId + idx,
|
||
|
url: sample,
|
||
|
timeout: initConfig.timeout * 1000 || _defaultTimeout,
|
||
|
nb: initConfig.occurrences || _defaultOccurrencesCount
|
||
|
};
|
||
|
});
|
||
|
|
||
|
// add one occurrence on the first sample to obfuscate the time needed to load the runner
|
||
|
_samples[0].nb++;
|
||
|
|
||
|
return {
|
||
|
/**
|
||
|
* Performs a performances test, then call a function to provide the result
|
||
|
* @param {Function} done
|
||
|
*/
|
||
|
start(done) {
|
||
|
const tests = [];
|
||
|
|
||
|
_.forEach(_samples, data => {
|
||
|
const cb = _.partial(loadItem, data);
|
||
|
let iterations = data.nb || 1;
|
||
|
while (iterations--) {
|
||
|
tests.push(cb);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
async.series(tests, (err, measures) => {
|
||
|
const decimals = 2;
|
||
|
|
||
|
if (err && !measures.length) {
|
||
|
//something went wrong
|
||
|
throw err;
|
||
|
}
|
||
|
|
||
|
// remove the first result to obfuscate the time needed to load the runner
|
||
|
measures.shift();
|
||
|
|
||
|
const results = stats(measures, 'duration', decimals);
|
||
|
const status = this.getFeedback(results.average);
|
||
|
const summary = this.getSummary(results);
|
||
|
|
||
|
done(status, summary, results);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Gets the labels loaded for the tester
|
||
|
* @returns {object}
|
||
|
*/
|
||
|
get labels() {
|
||
|
return labels;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Builds the results summary
|
||
|
* @param {object} results
|
||
|
* @returns {object}
|
||
|
*/
|
||
|
getSummary(results) {
|
||
|
return {
|
||
|
performancesMin: { message: labels.performancesMin, value: `${results.min} s` },
|
||
|
performancesMax: { message: labels.performancesMax, value: `${results.max} s` },
|
||
|
performancesAverage: { message: labels.performancesAverage, value: `${results.average} s` }
|
||
|
};
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Gets the feedback status for the provided result value
|
||
|
* @param {Number} result
|
||
|
* @returns {object}
|
||
|
*/
|
||
|
getFeedback(result) {
|
||
|
const optimal = initConfig.optimal;
|
||
|
const range = Math.abs(optimal - initConfig.threshold);
|
||
|
const status = getStatus(((range + optimal - result) / range) * 100, _thresholds);
|
||
|
|
||
|
status.title = labels.title;
|
||
|
status.id = initConfig.id;
|
||
|
return status;
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
});
|