"use strict";/*jslint vars: true, plusplus: true*//*globals Script, MyAvatar, Quat, Render, ScriptDiscoveryService, Window, LODManager, Entities, print*///// loadedMachine.js// scripts/developer/tests///// Created by Howard Stearns on 6/6/16.// Copyright 2016 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//// Characterises the performance of heavily loaded or struggling machines.// There is no point in running this on a machine that producing the target 60 or 90 hz rendering rate.//// The script examines several source of load, including each running script and a couple of Entity types.// It turns all of these off, as well as LOD management, and twirls in place to get a baseline render rate.// Then it turns each load on, one at a time, and records a new render rate.// At the end, it reports the ordered results (in a popup), restores the original loads and LOD management, and quits.//// Small performance changes are not meaningful.// If you think you find something that is significant, you should probably run again to make sure that it isn't random variation.// You can also compare (e.g., the baseline) for different conditions such as domain, version, server settings, etc.// This does not profile scripts as they are used -- it merely measures the effect of having the script loaded and running quietly. var NUMBER_OF_TWIRLS_PER_LOAD = 10;var MILLISECONDS_PER_SECOND = 1000;var WAIT_TIME_MILLISECONDS = MILLISECONDS_PER_SECOND;var accumulatedRotation = 0;var start;var frames = 0;var config = Render.getConfig("Stats");var thisPath = Script.resolvePath('');var scriptData = ScriptDiscoveryService.getRunning();var scripts = scriptData.filter(function (datum) { return datum.name !== 'defaultScripts.js'; }).map(function (script) { return script.path; });// If defaultScripts.js is running, we leave it running, because restarting it safely is a mess.var otherScripts = scripts.filter(function (path) { return path !== thisPath; });var numberLeftRunning = scriptData.length - otherScripts.length;print('initially running', otherScripts.length, 'scripts. Leaving', numberLeftRunning, 'and stopping otherScripts');var typedEntities = {Light: [], ParticleEffect: []};var interestingTypes = Object.keys(typedEntities);var propertiedEntities = {dynamic: []};var interestingProperties = Object.keys(propertiedEntities);var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes, interestingProperties);var loadIndex = 0, nLoads = loads.length, load;var results = [];var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust();var SEARCH_RADIUS = 17000;var DEFAULT_LOD = 32768 * 400;LODManager.setAutomaticLODAdjust(false);LODManager.setOctreeSizeScale(DEFAULT_LOD); // Fill the typedEnties with the entityIDs that are already visible. It would be nice if this were more efficient.var allEntities = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS);print('Searching', allEntities.length, 'entities for', interestingTypes, 'and', interestingProperties);var propertiesToGet = ['type', 'visible'].concat(interestingProperties);allEntities.forEach(function (entityID) { var properties = Entities.getEntityProperties(entityID, propertiesToGet); if (properties.visible) { if (interestingTypes.indexOf(properties.type) >= 0) { typedEntities[properties.type].push(entityID); } else { interestingProperties.forEach(function (property) { if (entityID && properties[property]) { propertiedEntities[property].push(entityID); entityID = false; // Put in only one bin } }); } }});allEntities = undefined; // free theminterestingTypes.forEach(function (type) { print('There are', typedEntities[type].length, type, 'entities.');});interestingProperties.forEach(function (property) { print('There are', propertiedEntities[property].length, property, 'entities.');});function toggleVisibility(type, on) { typedEntities[type].forEach(function (entityID) { Entities.editEntity(entityID, {visible: on}); });}function toggleProperty(property, on) { propertiedEntities[property].forEach(function (entityID) { var properties = {}; properties[property] = on; Entities.editEntity(entityID, properties); });}function restoreOneTest(load) { print('restore', load); switch (load) { case 'baseline': case 'ignore': // ignore is used to do a twirl to make sure everything is loaded. break; case 'Light': case 'ParticleEffect': toggleVisibility(load, true); break; case 'dynamic': toggleProperty(load, 1); break; default: Script.load(load); }}function undoOneTest(load) { print('stop', load); switch (load) { case 'baseline': case 'ignore': break; case 'Light': case 'ParticleEffect': toggleVisibility(load, false); break; case 'dynamic': toggleProperty(load, 0); break; default: ScriptDiscoveryService.stopScript(load); }}loads.forEach(undoOneTest); function finalReport() { var baseline = results[0]; results.forEach(function (result) { result.ratio = (baseline.fps / result.fps); }); results.sort(function (a, b) { return b.ratio - a.ratio; }); var report = 'Performance Report:\nBaseline: ' + baseline.fps.toFixed(1) + ' fps.'; results.forEach(function (result) { report += '\n' + result.ratio.toFixed(2) + ' (' + result.fps.toFixed(1) + ' fps over ' + result.elapsed + ' seconds) for ' + result.name; }); Window.alert(report); LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); loads.forEach(restoreOneTest); Script.stop();} function onNewStats() { // Accumulates frames on signal during load test frames++;}var DEGREES_PER_FULL_TWIRL = 360;var DEGREES_PER_UPDATE = 6;function onUpdate() { // Spins on update signal during load test MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation, 0); accumulatedRotation += DEGREES_PER_UPDATE; if (accumulatedRotation >= (DEGREES_PER_FULL_TWIRL * NUMBER_OF_TWIRLS_PER_LOAD)) { tearDown(); }}function startTest() { print('start', load); accumulatedRotation = frames = 0; start = Date.now(); Script.update.connect(onUpdate); config.newStats.connect(onNewStats);} function setup() { if (loadIndex >= nLoads) { return finalReport(); } load = loads[loadIndex]; restoreOneTest(load); Script.setTimeout(startTest, WAIT_TIME_MILLISECONDS);}function tearDown() { var now = Date.now(); var elapsed = (now - start) / MILLISECONDS_PER_SECOND; Script.update.disconnect(onUpdate); config.newStats.disconnect(onNewStats); if (load !== 'ignore') { results.push({name: load, fps: frames / elapsed, elapsed: elapsed}); } undoOneTest(load); loadIndex++; setup();}function waitUntilStopped() { // Wait until all the scripts have stopped var count = ScriptDiscoveryService.getRunning().length; if (count === numberLeftRunning) { return setup(); } print('Still', count, 'scripts running'); Script.setTimeout(waitUntilStopped, WAIT_TIME_MILLISECONDS);}waitUntilStopped();
"use strict";
/*jslint vars: true, plusplus: true*/
/*globals Script, MyAvatar, Quat, Render, ScriptDiscoveryService, Window, LODManager, Entities, print*/
//
// loadedMachine.js
// scripts/developer/tests/
//
// Created by Howard Stearns on 6/6/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Characterises the performance of heavily loaded or struggling machines.
// There is no point in running this on a machine that producing the target 60 or 90 hz rendering rate.
//
// The script examines several source of load, including each running script and a couple of Entity types.
// It turns all of these off, as well as LOD management, and twirls in place to get a baseline render rate.
// Then it turns each load on, one at a time, and records a new render rate.
// At the end, it reports the ordered results (in a popup), restores the original loads and LOD management, and quits.
//
// Small performance changes are not meaningful.
// If you think you find something that is significant, you should probably run again to make sure that it isn't random variation.
// You can also compare (e.g., the baseline) for different conditions such as domain, version, server settings, etc.
// This does not profile scripts as they are used -- it merely measures the effect of having the script loaded and running quietly.
var NUMBER_OF_TWIRLS_PER_LOAD = 10;
var MILLISECONDS_PER_SECOND = 1000;
var WAIT_TIME_MILLISECONDS = MILLISECONDS_PER_SECOND;
var accumulatedRotation = 0;
var start;
var frames = 0;
var config = Render.getConfig("Stats");
var thisPath = Script.resolvePath('');
var scriptData = ScriptDiscoveryService.getRunning();
var scripts = scriptData.filter(function (datum) { return datum.name !== 'defaultScripts.js'; }).map(function (script) { return script.path; });
// If defaultScripts.js is running, we leave it running, because restarting it safely is a mess.
var otherScripts = scripts.filter(function (path) { return path !== thisPath; });
var numberLeftRunning = scriptData.length - otherScripts.length;
print('initially running', otherScripts.length, 'scripts. Leaving', numberLeftRunning, 'and stopping otherScripts');
var typedEntities = {Light: [], ParticleEffect: []};
var interestingTypes = Object.keys(typedEntities);
var propertiedEntities = {dynamic: []};
var interestingProperties = Object.keys(propertiedEntities);
var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes, interestingProperties);
var loadIndex = 0, nLoads = loads.length, load;
var results = [];
var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust();
var SEARCH_RADIUS = 17000;
var DEFAULT_LOD = 32768 * 400;
LODManager.setAutomaticLODAdjust(false);
LODManager.setOctreeSizeScale(DEFAULT_LOD);
// Fill the typedEnties with the entityIDs that are already visible. It would be nice if this were more efficient.
var allEntities = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS);
print('Searching', allEntities.length, 'entities for', interestingTypes, 'and', interestingProperties);
var propertiesToGet = ['type', 'visible'].concat(interestingProperties);
allEntities.forEach(function (entityID) {
var properties = Entities.getEntityProperties(entityID, propertiesToGet);
if (properties.visible) {
if (interestingTypes.indexOf(properties.type) >= 0) {
typedEntities[properties.type].push(entityID);
} else {
interestingProperties.forEach(function (property) {
if (entityID && properties[property]) {
propertiedEntities[property].push(entityID);
entityID = false; // Put in only one bin
}
});
}
}
});
allEntities = undefined; // free them
interestingTypes.forEach(function (type) {
print('There are', typedEntities[type].length, type, 'entities.');
});
interestingProperties.forEach(function (property) {
print('There are', propertiedEntities[property].length, property, 'entities.');
});
function toggleVisibility(type, on) {
typedEntities[type].forEach(function (entityID) {
Entities.editEntity(entityID, {visible: on});
});
}
function toggleProperty(property, on) {
propertiedEntities[property].forEach(function (entityID) {
var properties = {};
properties[property] = on;
Entities.editEntity(entityID, properties);
});
}
function restoreOneTest(load) {
print('restore', load);
switch (load) {
case 'baseline':
case 'ignore': // ignore is used to do a twirl to make sure everything is loaded.
break;
case 'Light':
case 'ParticleEffect':
toggleVisibility(load, true);
break;
case 'dynamic':
toggleProperty(load, 1);
break;
default:
Script.load(load);
}
}
function undoOneTest(load) {
print('stop', load);
switch (load) {
case 'baseline':
case 'ignore':
break;
case 'Light':
case 'ParticleEffect':
toggleVisibility(load, false);
break;
case 'dynamic':
toggleProperty(load, 0);
break;
default:
ScriptDiscoveryService.stopScript(load);
}
}
loads.forEach(undoOneTest);
function finalReport() {
var baseline = results[0];
results.forEach(function (result) {
result.ratio = (baseline.fps / result.fps);
});
results.sort(function (a, b) { return b.ratio - a.ratio; });
var report = 'Performance Report:\nBaseline: ' + baseline.fps.toFixed(1) + ' fps.';
results.forEach(function (result) {
report += '\n' + result.ratio.toFixed(2) + ' (' + result.fps.toFixed(1) + ' fps over ' + result.elapsed + ' seconds) for ' + result.name;
});
Window.alert(report);
LODManager.setAutomaticLODAdjust(initialLodIsAutomatic);
loads.forEach(restoreOneTest);
Script.stop();
}
function onNewStats() { // Accumulates frames on signal during load test
frames++;
}
var DEGREES_PER_FULL_TWIRL = 360;
var DEGREES_PER_UPDATE = 6;
function onUpdate() { // Spins on update signal during load test
MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation, 0);
accumulatedRotation += DEGREES_PER_UPDATE;
if (accumulatedRotation >= (DEGREES_PER_FULL_TWIRL * NUMBER_OF_TWIRLS_PER_LOAD)) {
tearDown();
}
}
function startTest() {
print('start', load);
accumulatedRotation = frames = 0;
start = Date.now();
Script.update.connect(onUpdate);
config.newStats.connect(onNewStats);
}
function setup() {
if (loadIndex >= nLoads) {
return finalReport();
}
load = loads[loadIndex];
restoreOneTest(load);
Script.setTimeout(startTest, WAIT_TIME_MILLISECONDS);
}
function tearDown() {
var now = Date.now();
var elapsed = (now - start) / MILLISECONDS_PER_SECOND;
Script.update.disconnect(onUpdate);
config.newStats.disconnect(onNewStats);
if (load !== 'ignore') {
results.push({name: load, fps: frames / elapsed, elapsed: elapsed});
}
undoOneTest(load);
loadIndex++;
setup();
}
function waitUntilStopped() { // Wait until all the scripts have stopped
var count = ScriptDiscoveryService.getRunning().length;
if (count === numberLeftRunning) {
return setup();
}
print('Still', count, 'scripts running');
Script.setTimeout(waitUntilStopped, WAIT_TIME_MILLISECONDS);
}
waitUntilStopped();