Modules (188)

PerfUtils

Description

This is a collection of utility functions for gathering performance data.

Dependencies

Variables

Private

_reentTests

    var _reentTests = {};

activeTests

Active tests. This is a hash of all tests that have had markStart() called, but have not yet had addMeasurement() called.

    var activeTests = {};

enabled

Flag to enable/disable performance data gathering. Default is true (enabled)

Type
boolean
    var enabled = brackets && !!brackets.app.getElapsedMilliseconds;

perfData

Performance data is stored in this hash object. The key is the name of the test (passed to markStart/addMeasurement), and the value is the time, in milliseconds, that it took to run the test. If multiple runs of the same test are made, the value is an Array with each run stored as an entry in the Array.

    var perfData = {};

updatableTests

Updatable tests. This is a hash of all tests that have had markStart() called, and have had updateMeasurement() called. Caller must explicitly remove tests from this list using finalizeMeasurement()

    var updatableTests = {};

Functions

Private

PerfMeasurement

id (string,undefined)
Unique ID for this measurement name
name non-nullable string
A short name for this measurement
reent nullable number
Sequence identifier for parallel tests of the same name
    function PerfMeasurement(id, name, reent) {
        this.name = name;
        this.reent = reent;
        if (id) {
            this.id = id;
        } else {
            this.id = (reent) ? "[reent " + this.reent + "] " + name : name;
        }
    }
Private

_generatePerfMeasurements

    function _generatePerfMeasurements(name) {
        // always convert it to array so that the rest of the routines could rely on it
        var id = (!Array.isArray(name)) ? [name] : name;
        // generate unique identifiers for each name
        var i;
        for (i = 0; i < id.length; i++) {
            if (!(id[i] instanceof PerfMeasurement)) {
                if (_reentTests[id[i]] === undefined) {
                    _reentTests[id[i]] = 0;
                } else {
                    _reentTests[id[i]]++;
                }
                id[i] = new PerfMeasurement(undefined, id[i], _reentTests[id[i]]);
            }
        }
        return id;
    }
Private

_markStart

id Object
Timer id.
time number
Timer start time.
    function _markStart(id, time) {
        if (activeTests[id.id]) {
            console.error("Recursive tests with the same id are not supported. Timer id: " + id.id);
        }

        activeTests[id.id] = { startTime: time };
    }
Public API

addMeasurement

Stop a timer and add its measurements to the performance data.

Multiple measurements can be stored for any given name. If there are multiple values for a name, they are stored in an Array.

If markStart() was not called for the specified timer, the measured time is relative to app startup.

id Object
Timer id.
    function addMeasurement(id) {
        if (!enabled) {
            return;
        }

        if (!(id instanceof PerfMeasurement)) {
            id = new PerfMeasurement(id, id);
        }

        var elapsedTime = brackets.app.getElapsedMilliseconds();

        if (activeTests[id.id]) {
            elapsedTime -= activeTests[id.id].startTime;
            delete activeTests[id.id];
        }

        if (perfData[id]) {
            // We have existing data, add to it
            if (Array.isArray(perfData[id])) {
                perfData[id].push(elapsedTime);
            } else {
                // Current data is a number, convert to Array
                perfData[id] = [perfData[id], elapsedTime];
            }
        } else {
            perfData[id] = elapsedTime;
        }

        if (id.reent !== undefined) {
            if (_reentTests[id] === 0) {
                delete _reentTests[id];
            } else {
                _reentTests[id]--;
            }
        }

    }
Public API

clear

Clear all logs including metric data and active tests.

    function clear() {
        perfData = {};
        activeTests = {};
        updatableTests = {};
        _reentTests = {};
    }

    // create performance measurement constants
    createPerfMeasurement("INLINE_WIDGET_OPEN", "Open inline editor or docs");
    createPerfMeasurement("INLINE_WIDGET_CLOSE", "Close inline editor or docs");

    // extensions may create additional measurement constants during their lifecycle

    exports.addMeasurement          = addMeasurement;
    exports.finalizeMeasurement     = finalizeMeasurement;
    exports.isActive                = isActive;
    exports.markStart               = markStart;
    exports.getData                 = getData;
    exports.searchData              = searchData;
    exports.updateMeasurement       = updateMeasurement;
    exports.getDelimitedPerfData    = getDelimitedPerfData;
    exports.createPerfMeasurement   = createPerfMeasurement;
    exports.clear                   = clear;
    exports.getHealthReport         = getHealthReport;
});
Public API

createPerfMeasurement

Create a new PerfMeasurement key. Adds itself to the module export. Can be accessed on the module, e.g. PerfUtils.MY_PERF_KEY.

id non-nullable string
Unique ID for this measurement name
name non-nullable name
A short name for this measurement
    function createPerfMeasurement(id, name) {
        var pm = new PerfMeasurement(id, name);
        exports[id] = pm;

        return pm;
    }
Public API

finalizeMeasurement

Remove timer from lists so next action starts a new measurement

updateMeasurement may not have been called, so timer may be in either or neither list, but should never be in both.

id Object
Timer id.
    function finalizeMeasurement(id) {
        if (activeTests[id.id]) {
            delete activeTests[id.id];
        }

        if (updatableTests[id.id]) {
            delete updatableTests[id.id];
        }
    }
Public API

getData

Returns the measured value for the given measurement name.

id Object
The measurement to retreive.
    function getData(id) {
        if (!id) {
            return perfData;
        }

        return perfData[id];
    }
Public API

getDelimitedPerfData

Returns the performance data as a tab delimited string

Returns: string
    function getDelimitedPerfData() {
        var result = "";
        _.forEach(perfData, function (entry, testName) {
            result += getValueAsString(entry) + "\t" + testName + "\n";
        });

        return result;
    }
Public API

getHealthReport

Returns the Performance metrics to be logged for health report

Returns: Object
An object with the health data logs to be sent
    function getHealthReport() {
        var healthReport = {
            projectLoadTimes : "",
            fileOpenTimes : ""
        };

        _.forEach(perfData, function (entry, testName) {
            if (StringUtils.startsWith(testName, "Application Startup")) {
                healthReport.AppStartupTime = getValueAsString(entry);
            } else if (StringUtils.startsWith(testName, "brackets module dependencies resolved")) {
                healthReport.ModuleDepsResolved = getValueAsString(entry);
            } else if (StringUtils.startsWith(testName, "Load Project")) {
                healthReport.projectLoadTimes += ":" + getValueAsString(entry, true);
            } else if (StringUtils.startsWith(testName, "Open File")) {
                healthReport.fileOpenTimes += ":" + getValueAsString(entry, true);
            }
        });

        return healthReport;
    }

    function searchData(regExp) {
        var keys = Object.keys(perfData).filter(function (key) {
            return regExp.test(key);
        });

        var datas = [];

        keys.forEach(function (key) {
            datas.push(perfData[key]);
        });

        return datas;
    }

getValueAsString

return single value, or comma separated values for an array or return aggregated values with

<min value, average, max value, standard deviation>

entry Array
An array or a single value
aggregateStats Boolean
If set, the returned value will be aggregated in the form -
Returns: String
a single value, or comma separated values in an array or if aggregateStats is set
    function getValueAsString(entry, aggregateStats) {
        if (!Array.isArray(entry)) {
            return entry;
        }

        if (aggregateStats) {
            var sum = 0,
                avg,
                min = _.min(entry),
                max = _.max(entry),
                sd,
                variationSum = 0;

            entry.forEach(function (value) {
                sum += value;
            });
            avg = Math.round(sum / entry.length);
            entry.forEach(function (value) {
                variationSum += Math.pow(value - avg, 2);
            });
            sd = Math.round(Math.sqrt(variationSum / entry.length));
            return min + "(" + avg + ")" + max + "[" + sd + "]";
        } else {
            return entry.join(", ");
        }
    }
Public API

isActive

Returns whether a timer is active or not, where "active" means that timer has been started with addMark(), but has not been added to perfdata with addMeasurement().

id Object
Timer id.
Returns: boolean
Whether a timer is active or not.
    function isActive(id) {
        return (activeTests[id.id]) ? true : false;
    }
Public API

markStart

Start a new named timer. The name should be as descriptive as possible, since this name will appear as an entry in the performance report. For example: "Open file: /Users/brackets/src/ProjectManager.js"

Multiple timers can be opened simultaneously.

Returns an opaque set of timer ids which can be stored and used for calling addMeasurement(). Since name is often creating via concatenating strings this return value allows clients to construct the name once.

name (string,Array.<string>)
Single name or an Array of names.
Returns: (Object,Array.<Object>)
Opaque timer id or array of timer ids.
    function markStart(name) {
        if (!enabled) {
            return;
        }

        var time = brackets.app.getElapsedMilliseconds();
        var id = _generatePerfMeasurements(name);
        var i;

        for (i = 0; i < id.length; i++) {
            _markStart(id[i], time);
        }
        return id.length > 1 ? id : id[0];
    }
Public API

updateMeasurement

This function is similar to addMeasurement(), but it allows timing the last event, when you don't know which event will be the last one.

Tests that are in the activeTests list, have not yet been added, so add measurements to the performance data, and move test to updatableTests list. A test is moved to the updatable list so that it no longer passes isActive().

Tests that are already in the updatableTests list are updated.

Caller must explicitly remove test from the updatableTests list using finalizeMeasurement().

If markStart() was not called for the specified timer, there is no way to determine if this is the first or subsequent call, so the measurement is not updatable, and it is handled in addMeasurement().

id Object
Timer id.
    function updateMeasurement(id) {
        var elapsedTime = brackets.app.getElapsedMilliseconds();

        if (updatableTests[id.id]) {
            // update existing measurement
            elapsedTime -= updatableTests[id].startTime;

            // update
            if (perfData[id] && Array.isArray(perfData[id])) {
                // We have existing data and it's an array, so update the last entry
                perfData[id][perfData[id].length - 1] = elapsedTime;
            } else {
                // No current data or a single entry, so set/update it
                perfData[id] = elapsedTime;
            }

        } else {
            // not yet in updatable list

            if (activeTests[id.id]) {
                // save startTime in updatable list before addMeasurement() deletes it
                updatableTests[id.id] = { startTime: activeTests[id.id].startTime };
            }

            // let addMeasurement() handle the initial case
            addMeasurement(id);
        }
    }