diff --git a/scripts/developer/tests/loadedMachine.js b/scripts/developer/tests/loadedMachine.js new file mode 100644 index 0000000000..c222eb7379 --- /dev/null +++ b/scripts/developer/tests/loadedMachine.js @@ -0,0 +1,163 @@ +"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 loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes); +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); +allEntities.forEach(function (entityID) { + var properties = Entities.getEntityProperties(entityID, ['type', 'visible']); + if (properties.visible && (interestingTypes.indexOf(properties.type) >= 0)) { + typedEntities[properties.type].push(entityID); + } +}); +interestingTypes.forEach(function (type) { + print('There are', typedEntities[type].length, type, 'entities.'); +}); +function toggleVisibility(type, on) { + typedEntities[type].forEach(function (entityID) { + Entities.editEntity(entityID, {visible: on}); + }); +} +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; + 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; + 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();