overte/scripts/developer/tests/loadedMachine.js
2018-11-06 15:09:24 -08:00

192 lines
7.3 KiB
JavaScript

"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();