UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

610 lines (556 loc) 18.9 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ /* * IMPORTANT: This is a private module, its API must not be used and is subject to change. * Code other than the OpenUI5 libraries must not introduce dependencies to this module. */ /*global XMLHttpRequest, document, location, window */ sap.ui.define(['sap/base/Log', 'sap/ui/thirdparty/URI', 'sap/base/util/now' ], function(Log, URI, now) { "use strict"; var URI = window.URI; /** * Performance Measurement API. * * @namespace * @since 1.58 * @name module:sap/ui/performance/Measurement * @public */ function PerfMeasurement() { /** * Single Measurement Entry. * * @public * @typedef {object} module:sap/ui/performance/Measurement.Entry * @property {string} sId ID of the measurement * @property {string} sInfo Info for the measurement * @property {int} iStart Start time * @property {int} iEnd End time * @property {string | string[]} [aCategories="javascript"] An optional list of categories for the measure */ function Measurement(sId, sInfo, iStart, iEnd, aCategories) { this.id = sId; this.info = sInfo; this.start = iStart; this.end = iEnd; this.pause = 0; this.resume = 0; this.duration = 0; // used time this.time = 0; // time from start to end this.categories = aCategories; this.average = false; //average duration enabled this.count = 0; //average count this.completeDuration = 0; //complete duration } function matchCategories(aCategories) { if (!aRestrictedCategories) { return true; } if (!aCategories) { return aRestrictedCategories === null; } //check whether active categories and current categories match for (var i = 0; i < aRestrictedCategories.length; i++) { if (aCategories.indexOf(aRestrictedCategories[i]) > -1) { return true; } } return false; } function checkCategories(aCategories) { if (!aCategories) { aCategories = ["javascript"]; } aCategories = typeof aCategories === "string" ? aCategories.split(",") : aCategories; if (!matchCategories(aCategories)) { return null; } return aCategories; } function hasCategory(oMeasurement, aCategories) { for (var i = 0; i < aCategories.length; i++) { if (oMeasurement.categories.indexOf(aCategories[i]) > -1) { return true; } } return aCategories.length === 0; } var bActive = false, fnXHR = XMLHttpRequest, aRestrictedCategories = null, aAverageMethods = [], aOriginalMethods = [], mMethods = {}, mMeasurements = {}; /** * Gets the current state of the performance measurement functionality. * * @return {boolean} current state of the performance measurement functionality * @public * @name module:sap/ui/performance/Measurement.getActive * @function */ this.getActive = function() { return bActive; }; /** * Activates or deactivates the performance measure functionality. * * Optionally a category or list of categories can be passed to restrict measurements to certain categories * like "javascript", "require", "xmlhttprequest", "render" * @param {boolean} bOn - state of the performance measurement functionality to set * @param {string | string[]} aCategories - An optional list of categories that should be measured * @return {boolean} current state of the performance measurement functionality * @public * @name module:sap/ui/performance/Measurement.setActive * @function */ this.setActive = function(bOn, aCategories) { var fnEnd, fnStart; //set restricted categories if (!aCategories) { aCategories = null; } else if (typeof aCategories === "string") { aCategories = aCategories.split(","); } aRestrictedCategories = aCategories; if (bActive === bOn) { return; } bActive = bOn; if (bActive) { //activate method implementations once for (var sName in mMethods) { this[sName] = mMethods[sName].bind(this); } mMethods = {}; fnEnd = this.end; fnStart = this.start; // wrap and instrument XHR /* eslint-disable-next-line no-global-assign */ XMLHttpRequest = function() { var oXHR = new fnXHR(), fnOpen = oXHR.open, sMeasureId; oXHR.open = function() { sMeasureId = new URI(arguments[1], new URI(document.baseURI).search("")).href(); fnStart(sMeasureId, "Request for " + sMeasureId, "xmlhttprequest"); oXHR.addEventListener("loadend", fnEnd.bind(null, sMeasureId)); fnOpen.apply(this, arguments); }; return oXHR; }; } else { /* eslint-disable-next-line no-global-assign */ XMLHttpRequest = fnXHR; } return bActive; }; /** * Starts a performance measure. * * Optionally a category or list of categories can be passed to allow filtering of measurements. * * @param {string} sId ID of the measurement * @param {string} sInfo Info for the measurement * @param {string | string[]} [aCategories="javascript"] An optional list of categories for the measure * @return {object} current measurement containing id, info and start-timestamp (false if error) * @public * @name module:sap/ui/performance/Measurement.start * @function */ mMethods["start"] = function(sId, sInfo, aCategories) { if (!bActive) { return; } aCategories = checkCategories(aCategories); if (!aCategories) { return; } var iTime = now(), oMeasurement = new Measurement(sId, sInfo, iTime, 0, aCategories); // create timeline entries if available /*eslint-disable no-console */ if (Log.getLevel("sap.ui.Performance") >= 4 && window.console && console.time) { console.time(sInfo + " - " + sId); } /*eslint-enable no-console */ Log.info("Performance measurement start: " + sId + " on " + iTime); if (oMeasurement) { mMeasurements[sId] = oMeasurement; return this.getMeasurement(oMeasurement.id); } else { return false; } }; /** * Pauses a performance measure. * * @param {string} sId ID of the measurement * @return {object} current measurement containing id, info and start-timestamp, pause-timestamp (false if error) * @public * @name module:sap/ui/performance/Measurement.pause * @function */ mMethods["pause"] = function(sId) { if (!bActive) { return; } var iTime = now(); var oMeasurement = mMeasurements[sId]; if (oMeasurement && oMeasurement.end > 0) { // already ended -> no pause possible return false; } if (oMeasurement && oMeasurement.pause == 0) { // not already paused oMeasurement.pause = iTime; if (oMeasurement.pause >= oMeasurement.resume && oMeasurement.resume > 0) { oMeasurement.duration = oMeasurement.duration + oMeasurement.pause - oMeasurement.resume; oMeasurement.resume = 0; } else if (oMeasurement.pause >= oMeasurement.start) { oMeasurement.duration = oMeasurement.pause - oMeasurement.start; } } if (oMeasurement) { Log.info("Performance measurement pause: " + sId + " on " + iTime + " duration: " + oMeasurement.duration); return this.getMeasurement(oMeasurement.id); } else { return false; } }; /** * Resumes a performance measure. * * @param {string} sId ID of the measurement * @return {object} current measurement containing id, info and start-timestamp, resume-timestamp (false if error) * @public * @name module:sap/ui/performance/Measurement.resume * @function */ mMethods["resume"] = function(sId) { if (!bActive) { return; } var iTime = now(); var oMeasurement = mMeasurements[sId]; if (oMeasurement && oMeasurement.pause > 0) { // already paused oMeasurement.pause = 0; oMeasurement.resume = iTime; } if (oMeasurement) { Log.info("Performance measurement resume: " + sId + " on " + iTime + " duration: " + oMeasurement.duration); return this.getMeasurement(oMeasurement.id); } else { return false; } }; /** * Ends a performance measure. * * @param {string} sId ID of the measurement * @return {object} current measurement containing id, info and start-timestamp, end-timestamp, time, duration (false if error) * @public * @name module:sap/ui/performance/Measurement.end * @function */ mMethods["end"] = function(sId) { if (!bActive) { return; } var iTime = now(); var oMeasurement = mMeasurements[sId]; if (oMeasurement && !oMeasurement.end) { Log.info("Performance measurement end: " + sId + " on " + iTime); oMeasurement.end = iTime; if (oMeasurement.end >= oMeasurement.resume && oMeasurement.resume > 0) { oMeasurement.duration = oMeasurement.duration + oMeasurement.end - oMeasurement.resume; oMeasurement.resume = 0; } else if (oMeasurement.pause > 0) { // duration already calculated oMeasurement.pause = 0; } else if (oMeasurement.end >= oMeasurement.start) { if (oMeasurement.average) { oMeasurement.completeDuration += (oMeasurement.end - oMeasurement.start); oMeasurement.count++; oMeasurement.duration = oMeasurement.completeDuration / oMeasurement.count; oMeasurement.start = iTime; } else { oMeasurement.duration = oMeasurement.end - oMeasurement.start; } } if (oMeasurement.end >= oMeasurement.start) { oMeasurement.time = oMeasurement.end - oMeasurement.start; } } if (oMeasurement) { // end timeline entry /*eslint-disable no-console */ if (Log.getLevel("sap.ui.Performance") >= 4 && window.console && console.timeEnd) { console.timeEnd(oMeasurement.info + " - " + sId); } /*eslint-enable no-console */ return this.getMeasurement(sId); } else { return false; } }; /** * Clears all performance measurements. * * @public * @name module:sap/ui/performance/Measurement.clear * @function */ mMethods["clear"] = function() { mMeasurements = {}; }; /** * Removes a performance measure. * * @param {string} sId ID of the measurement * @public * @name module:sap/ui/performance/Measurement.remove * @function */ mMethods["remove"] = function(sId) { delete mMeasurements[sId]; }; /** * Adds a performance measurement with all data. * * This is useful to add external measurements (e.g. from a backend) to the common measurement UI * * @param {string} sId ID of the measurement * @param {string} sInfo Info for the measurement * @param {int} iStart start timestamp * @param {int} iEnd end timestamp * @param {int} iTime time in milliseconds * @param {int} iDuration effective time in milliseconds * @param {string | string[]} [aCategories="javascript"] An optional list of categories for the measure * @return {object} [] current measurement containing id, info and start-timestamp, end-timestamp, time, duration, categories (false if error) * @public * @name module:sap/ui/performance/Measurement.add * @function */ mMethods["add"] = function(sId, sInfo, iStart, iEnd, iTime, iDuration, aCategories) { if (!bActive) { return; } aCategories = checkCategories(aCategories); if (!aCategories) { return false; } var oMeasurement = new Measurement( sId, sInfo, iStart, iEnd, aCategories); oMeasurement.time = iTime; oMeasurement.duration = iDuration; if (oMeasurement) { mMeasurements[sId] = oMeasurement; return this.getMeasurement(oMeasurement.id); } else { return false; } }; /** * Starts an average performance measure. * * The duration of this measure is an avarage of durations measured for each call. * Optionally a category or list of categories can be passed to allow filtering of measurements. * * @param {string} sId ID of the measurement * @param {string} sInfo Info for the measurement * @param {string | string[]} [aCategories="javascript"] An optional list of categories for the measure * @return {object} current measurement containing id, info and start-timestamp (false if error) * @public * @name module:sap/ui/performance/Measurement.average * @function */ mMethods["average"] = function(sId, sInfo, aCategories) { if (!bActive) { return; } aCategories = checkCategories(aCategories); if (!aCategories) { return; } var oMeasurement = mMeasurements[sId], iTime = now(); if (!oMeasurement || !oMeasurement.average) { this.start(sId, sInfo, aCategories); oMeasurement = mMeasurements[sId]; oMeasurement.average = true; } else { if (!oMeasurement.end) { oMeasurement.completeDuration += (iTime - oMeasurement.start); oMeasurement.count++; } oMeasurement.start = iTime; oMeasurement.end = 0; } return this.getMeasurement(oMeasurement.id); }; /** * Gets a performance measure. * * @param {string} sId ID of the measurement * @return {module:sap/ui/performance/Measurement.Entry} current measurement containing id, info and start-timestamp, end-timestamp, time, duration (false if error) * @public * @name module:sap/ui/performance/Measurement.getMeasurement * @function */ this.getMeasurement = function(sId) { var oMeasurement = mMeasurements[sId]; if (oMeasurement) { // create a flat copy var oCopy = {}; for (var sProp in oMeasurement) { oCopy[sProp] = oMeasurement[sProp]; } return oCopy; } else { return false; } }; /** * Gets all performance measurements. * * @param {boolean} [bCompleted] Whether only completed measurements should be returned, if explicitly set to false only incomplete measurements are returned * @return {module:sap/ui/performance/Measurement.Entry} current array with measurements containing id, info and start-timestamp, end-timestamp, time, duration, categories * @public * @name module:sap/ui/performance/Measurement.getAllMeasurements * @function */ this.getAllMeasurements = function(bCompleted) { return this.filterMeasurements(function(oMeasurement) { return oMeasurement; }, bCompleted); }; /** * Gets all performance measurements where a provided filter function returns a truthy value. * * If neither a filter function nor a category is provided an empty array is returned. * To filter for certain properties of measurements a fnFilter can be implemented like this * <code> * function(oMeasurement) { * return oMeasurement.duration > 50; * }</code> * * @param {function} [fnFilter] a filter function that returns true if the passed measurement should be added to the result * @param {boolean} [bCompleted] Optional parameter to determine if either completed or incomplete measurements should be returned (both if not set or undefined) * @param {string[]} [aCategories] The function returns only measurements which match these specified categories * * @return {module:sap/ui/performance/Measurement.Entry[]} filtered array with measurements containing id, info and start-timestamp, end-timestamp, time, duration, categories (false if error) * @public * @name module:sap/ui/performance/Measurement.filterMeasurements * @function */ this.filterMeasurements = function() { var oMeasurement, bValid, i = 0, aMeasurements = [], fnFilter = typeof arguments[i] === "function" ? arguments[i++] : undefined, bCompleted = typeof arguments[i] === "boolean" ? arguments[i++] : undefined, aCategories = Array.isArray(arguments[i]) ? arguments[i] : []; for (var sId in mMeasurements) { oMeasurement = this.getMeasurement(sId); bValid = (bCompleted === false && oMeasurement.end === 0) || (bCompleted !== false && (!bCompleted || oMeasurement.end)); if (bValid && hasCategory(oMeasurement, aCategories) && (!fnFilter || fnFilter(oMeasurement))) { aMeasurements.push(oMeasurement); } } return aMeasurements; }; /** * Registers an average measurement for a given objects method. * * @param {string} sId the id of the measurement * @param {object} oObject the object of the method * @param {string} sMethod the name of the method * @param {string[]} [aCategories=["javascript"]] An optional categories list for the measurement * @returns {boolean} true if the registration was successful * @public * @name module:sap/ui/performance/Measurement.registerMethod * @function */ this.registerMethod = function(sId, oObject, sMethod, aCategories) { var fnMethod = oObject[sMethod]; if (fnMethod && typeof fnMethod === "function") { var bFound = aAverageMethods.indexOf(fnMethod) > -1; if (!bFound) { aOriginalMethods.push({func : fnMethod, obj: oObject, method: sMethod, id: sId}); var that = this; oObject[sMethod] = function() { that.average(sId, sId + " method average", aCategories); var result = fnMethod.apply(this, arguments); that.end(sId); return result; }; aAverageMethods.push(oObject[sMethod]); return true; } } else { Log.debug(sMethod + " in not a function. Measurement.register failed"); } return false; }; /** * Unregisters an average measurement for a given objects method. * * @param {string} sId the id of the measurement * @param {object} oObject the object of the method * @param {string} sMethod the name of the method * @returns {boolean} true if the unregistration was successful * @public * @name module:sap/ui/performance/Measurement.unregisterMethod * @function */ this.unregisterMethod = function(sId, oObject, sMethod) { var fnFunction = oObject[sMethod], iIndex = aAverageMethods.indexOf(fnFunction); if (fnFunction && iIndex > -1) { oObject[sMethod] = aOriginalMethods[iIndex].func; aAverageMethods.splice(iIndex, 1); aOriginalMethods.splice(iIndex, 1); return true; } return false; }; /** * Unregisters all average measurements. * * @public * @name module:sap/ui/performance/Measurement.unregisterAllMethods * @function */ this.unregisterAllMethods = function() { while (aOriginalMethods.length > 0) { var oOrig = aOriginalMethods[0]; this.unregisterMethod(oOrig.id, oOrig.obj, oOrig.method); } }; var aMatch = location.search.match(/sap-ui-measure=([^\&]*)/); if (aMatch && aMatch[1]) { if (aMatch[1] === "true" || aMatch[1] === "x" || aMatch[1] === "X") { this.setActive(true); } else { this.setActive(true, aMatch[1]); } } else { var fnInactive = function() { //measure not active return null; }; //deactivate methods implementations for (var sName in mMethods) { this[sName] = fnInactive; } } } return new PerfMeasurement(); });