From 51b351f5b61c013824d45c0a7400c8ebad47742e Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Fri, 2 Jun 2017 16:34:04 +0300 Subject: [PATCH] Add timeline traces in the console, let enabling them through the package.json --- tests/app/profiling/profiling-tests.ts | 81 ++++++--- .../application/application.android.ts | 41 ++--- tns-core-modules/profiling/profiling.d.ts | 42 ++++- tns-core-modules/profiling/profiling.ts | 163 ++++++++++-------- tns-core-modules/ui/button/button.android.ts | 3 +- .../ui/core/properties/properties.ts | 6 +- .../ui/core/view-base/view-base.ts | 15 ++ tns-core-modules/ui/core/view/view.android.ts | 4 + tns-core-modules/ui/core/view/view.ios.ts | 4 +- tns-core-modules/ui/frame/frame.android.ts | 19 ++ tns-core-modules/ui/frame/frame.ios.ts | 4 + tns-core-modules/ui/label/label.android.ts | 2 + .../ui/list-picker/list-picker.ios.ts | 2 + .../ui/list-view/list-view.android.ts | 3 + .../ui/list-view/list-view.ios.ts | 2 + tns-core-modules/ui/page/page-common.ts | 2 + tns-core-modules/ui/page/page.android.ts | 2 + tns-core-modules/ui/page/page.ios.ts | 2 + tns-core-modules/ui/repeater/repeater.ts | 2 + .../ui/scroll-view/scroll-view-common.ts | 2 + tns-core-modules/ui/tab-view/tab-view.ios.ts | 3 +- .../ui/text-field/text-field.ios.ts | 2 + .../ui/text-view/text-view.ios.ts | 2 + tns-core-modules/ui/web-view/web-view.ios.ts | 2 + 24 files changed, 282 insertions(+), 128 deletions(-) diff --git a/tests/app/profiling/profiling-tests.ts b/tests/app/profiling/profiling-tests.ts index 669962d2d..152c9bdcf 100644 --- a/tests/app/profiling/profiling-tests.ts +++ b/tests/app/profiling/profiling-tests.ts @@ -1,5 +1,5 @@ import { assert, assertEqual, assertFalse, assertTrue, assertThrows } from "../TKUnit"; -import { enable, disable, profile, time, start, stop, pause, isRunning } from "tns-core-modules/profiling"; +import { enable, disable, profile, time, start, stop, timer, isRunning, resetProfiles } from "tns-core-modules/profiling"; enable(); class TestClass { @@ -22,6 +22,20 @@ class TestClass { unnamed2() { // noop } + + private isInReentrant = false; + + @profile + reentrant() { + try { + if (!this.isInReentrant) { + this.isInReentrant = true; + this.reentrant(); + } + } finally { + this.isInReentrant = false; + } + } } const testFunction1 = profile(function testFunction1() { @@ -45,41 +59,54 @@ function retry(count: number, action: () => void) { } } -export function setUp() { - enable(); -} - -export function tearDown() { - disable(); -} - export function test_time_returns_number() { assertEqual(typeof time(), "number"); }; export function test_isRunning() { + resetProfiles(); const name = "test_isRunning"; assertFalse(isRunning(name), "isRunning should be false before start"); start(name); assertTrue(isRunning(name), "isRunning should be true after start"); - pause(name); - assertFalse(isRunning(name), "isRunning should be false after pause"); + stop(name); + assertFalse(isRunning(name), "isRunning should be false after stop"); start(name); assertTrue(isRunning(name), "isRunning should be true after second start"); stop(name); - assertFalse(isRunning(name), "isRunning should be false after stop"); + assertFalse(isRunning(name), "isRunning should be false after second stop"); +} + +export function test_isRunning_withReentrancy() { + resetProfiles(); + const name = "test_isRunning"; + assertFalse(isRunning(name), "isRunning should be false before start"); + + start(name); + assertTrue(isRunning(name), "isRunning should be true after start"); + + start(name); + assertTrue(isRunning(name), "isRunning should be true after second start"); + + stop(name); + assertTrue(isRunning(name), "isRunning should be true after first stop"); + + stop(name); + assertFalse(isRunning(name), "isRunning should be false after second stop"); } export function test_start_stop() { + resetProfiles(); retry(5, () => { const name = "test_start_stop"; start(name); - const res = stop(name); + stop(name); + const res = timer(name); assertEqual(res.count, 1); assert(res.totalTime <= 1); @@ -87,18 +114,20 @@ export function test_start_stop() { }; export function test_start_pause_count() { + resetProfiles(); const name = "test_start_pause_count"; for (var i = 0; i < 10; i++) { start(name); - pause(name); + stop(name); } - const res = stop(name); + const res = timer(name); assertEqual(res.count, 10); }; export function test_profile_decorator_count() { + resetProfiles(); const test = new TestClass(); for (var i = 0; i < 10; i++) { test.doNothing(); @@ -109,36 +138,39 @@ export function test_profile_decorator_count() { } ["__func_decorator__", "TestClass.unnamed1", "TestClass.unnamed2", "testFunction1", "testFunction2"].forEach(key => { - const res = stop(key); + const res = timer(key); assertEqual(res.count, 10, "Expected profile with name ${key} to have traced 10 calls."); }); } export function test_profile_decorator_handles_exceptions() { + resetProfiles(); const test = new TestClass(); assertThrows(() => test.throwError()); assertFalse(isRunning("__func_decorator_error__"), "Timer should be stopped on exception."); - assertEqual(stop("__func_decorator_error__").count, 1, "Timer should be called once"); + assertEqual(timer("__func_decorator_error__").count, 1, "Timer should be called once"); } export function test_start_pause_performance() { + resetProfiles(); retry(5, () => { const count = 10000; const name = "test_start_pause_performance"; for (var i = 0; i < count; i++) { start(name); - pause(name); + stop(name); } - const res = stop(name); + const res = timer(name); assertEqual(res.count, count); assert(res.totalTime <= 500, `Total time for ${count} timer operations is too much: ${res.totalTime}`); }); }; export function test_profile_decorator_performance() { + resetProfiles(); retry(5, () => { var start = Date.now(); const count = 10000; @@ -147,7 +179,7 @@ export function test_profile_decorator_performance() { test.doNothing(); } - const res = stop("__func_decorator__"); + const res = timer("__func_decorator__"); assertEqual(res.count, count); assert(res.totalTime <= 500, `Total time for ${count} timer operations is too much: ${res.totalTime}`); @@ -155,3 +187,12 @@ export function test_profile_decorator_performance() { assert(end - start <= 1000, `Total time for test execution is too much: ${end - start}ms`); }); } + +export function test_reentrancy() { + resetProfiles(); + // reentrant + retry(5, () => { + const test = new TestClass(); + test.reentrant(); + }); +} diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index d36319e1c..9e14f6439 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -7,6 +7,7 @@ import { notify, hasListeners, lowMemoryEvent, orientationChangedEvent, suspendEvent, resumeEvent, displayedEvent, setApplication, livesync, Observable } from "./application-common"; +import { profile } from "../profiling"; // First reexport so that app module is initialized. export * from "./application-common"; @@ -167,7 +168,7 @@ global.__onLiveSync = function () { function initLifecycleCallbacks() { // TODO: Verify whether the logic for triggerring application-wide events based on Activity callbacks is working properly const lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({ - onActivityCreated: function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) { + onActivityCreated: profile("onActivityCreated", function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) { // Set app theme after launch screen was used during startup const activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), android.content.pm.PackageManager.GET_META_DATA); if (activityInfo.metaData) { @@ -194,9 +195,9 @@ function initLifecycleCallbacks() { }); rootView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); } - }, + }), - onActivityDestroyed: function (activity: android.app.Activity) { + onActivityDestroyed: profile("onActivityDestroyed", function (activity: android.app.Activity) { if (activity === androidApp.foregroundActivity) { androidApp.foregroundActivity = undefined; } @@ -208,18 +209,18 @@ function initLifecycleCallbacks() { androidApp.notify({ eventName: ActivityDestroyed, object: androidApp, activity: activity }); // TODO: This is a temporary workaround to force the V8's Garbage Collector, which will force the related Java Object to be collected. gc(); - }, + }), - onActivityPaused: function (activity: android.app.Activity) { + onActivityPaused: profile("onActivityPaused", function (activity: android.app.Activity) { if ((activity).isNativeScriptActivity) { androidApp.paused = true; notify({ eventName: suspendEvent, object: androidApp, android: activity }); } androidApp.notify({ eventName: ActivityPaused, object: androidApp, activity: activity }); - }, + }), - onActivityResumed: function (activity: android.app.Activity) { + onActivityResumed: profile("onActivityResumed", function (activity: android.app.Activity) { androidApp.foregroundActivity = activity; if ((activity).isNativeScriptActivity) { @@ -228,19 +229,19 @@ function initLifecycleCallbacks() { } androidApp.notify({ eventName: ActivityResumed, object: androidApp, activity: activity }); - }, + }), - onActivitySaveInstanceState: function (activity: android.app.Activity, outState: android.os.Bundle) { + onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.app.Activity, outState: android.os.Bundle) { androidApp.notify({ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState }); - }, + }), - onActivityStarted: function (activity: android.app.Activity) { + onActivityStarted: profile("onActivityStarted", function (activity: android.app.Activity) { androidApp.notify({ eventName: ActivityStarted, object: androidApp, activity: activity }); - }, + }), - onActivityStopped: function (activity: android.app.Activity) { + onActivityStopped: profile("onActivityStopped", function (activity: android.app.Activity) { androidApp.notify({ eventName: ActivityStopped, object: androidApp, activity: activity }); - } + }) }); return lifecycleCallbacks; @@ -249,17 +250,17 @@ function initLifecycleCallbacks() { let currentOrientation: number; function initComponentCallbacks() { let componentCallbacks = new android.content.ComponentCallbacks2({ - onLowMemory: function () { + onLowMemory: profile("onLowMemory", function () { gc(); java.lang.System.gc(); notify({ eventName: lowMemoryEvent, object: this, android: this }); - }, + }), - onTrimMemory: function (level: number) { + onTrimMemory: profile("onTrimMemory", function (level: number) { // TODO: This is skipped for now, test carefully for OutOfMemory exceptions - }, + }), - onConfigurationChanged: function (newConfig: android.content.res.Configuration) { + onConfigurationChanged: profile("onConfigurationChanged", function (newConfig: android.content.res.Configuration) { const newOrientation = newConfig.orientation; if (newOrientation === currentOrientation) { return; @@ -286,7 +287,7 @@ function initComponentCallbacks() { newValue: newValue, object: androidApp }); - } + }) }); return componentCallbacks; diff --git a/tns-core-modules/profiling/profiling.d.ts b/tns-core-modules/profiling/profiling.d.ts index a3b485585..f60685151 100644 --- a/tns-core-modules/profiling/profiling.d.ts +++ b/tns-core-modules/profiling/profiling.d.ts @@ -10,9 +10,32 @@ interface TimerInfo { } /** - * Enables profiling. + * Profiling mode to use. + * - `counters` Accumulates method call counts and times until dumpProfiles is called and then prints agregated statistic in the console. This is the default. + * - `timeline` Outputs method names along start/end timestamps in the console on the go. */ -export declare function enable(): void; +type InstrumentationMode = "counters" | "timeline"; + +/** + * Enables profiling. + * + * Upon loading of the module it will cache the package.json of the app and check if there is a "profiling" key set, + * its value can be one of the options available for InstrumentationMode, and if set, + * enable() will be called in pre app start with the value in the package.json. + * + * An example for an `app/package.json` enabling the manual instrumentation profiling is: + * ``` + * { + * "main": "main.js", + * "profiling": "timeline" + * } + * ``` + * + * @param type Profiling mode to use. + * - "counters" - Accumulates method call counts and times until dumpProfiles is called and then prints agregated statistic in the console. This is the default. + * - "timeline" - Outputs method names along start/end timestamps in the console on the go. + */ +export declare function enable(type?: InstrumentationMode): void; /** * Disables profiling. @@ -37,15 +60,13 @@ export declare function start(name: string): void; * @param name Name of the timer. * @returns TimerInfo for the paused timer. */ -export declare function pause(name: string): TimerInfo; +export declare function stop(name: string): TimerInfo; /** - * Stops a timer with a specific name. This will print the count and the total time and will also reset the timer. - * Works only if profiling is enabled. - * @param name Name of the timer. - * @returns TimerInfo for the stopped timer. + * Read a timer info. + * @param name The name of the timer to obtain information about. */ -export declare function stop(name: string): TimerInfo; +export function timer(name: string): TimerInfo; /** * Returns true if a timer is currently running. @@ -60,18 +81,21 @@ export declare function isRunning(name: string): boolean; * @param name Name of the timer which will be used for method calls. If not provided - the name of the method will be used. */ export declare function profile(name?: string): MethodDecorator; + /** * Function factory. It will intercept the function call and start and pause a timer before and after the function call. Works only if profiling is enabled. * Works only if profiling is enabled. * @param fn The function to wrap. Uses the function name to track the times. */ export declare function profile(fn: F): F; + /** * Function factory. It will intercept the function call and start and pause a timer before and after the function call. Works only if profiling is enabled. * @param name The name used to track calls and times. * @param fn The function to wrap. */ export declare function profile(name: string, fn: F): F; + /** * Method decorator. It will intercept the method calls and start and pause a timer before and after the method call. Works only if profiling is enabled. */ @@ -103,4 +127,4 @@ export declare function stopCPUProfile(name: string): void; /** * Gets the uptime of the current process in miliseconds. */ -export function uptime(): number; \ No newline at end of file +export function uptime(): number; diff --git a/tns-core-modules/profiling/profiling.ts b/tns-core-modules/profiling/profiling.ts index bf6ac7597..4d9ce7ff5 100644 --- a/tns-core-modules/profiling/profiling.ts +++ b/tns-core-modules/profiling/profiling.ts @@ -1,7 +1,7 @@ declare var __startCPUProfiler: any; declare var __stopCPUProfiler: any; -import { TimerInfo as TimerInfoDefinition } from "."; +import { TimerInfo as TimerInfoDefinition, InstrumentationMode } from "."; export const uptime = global.android ? (org).nativescript.Process.getUpTime : (global).__tns_uptime; @@ -10,7 +10,10 @@ interface TimerInfo extends TimerInfoDefinition { lastTime?: number; count: number; currentStart: number; - isRunning: boolean; + /** + * Counts the number of entry and exits a function had. + */ + runCount: number; } // Use object instead of map as it is a bit faster @@ -18,87 +21,119 @@ const timers: { [index: string]: TimerInfo } = {}; const anyGlobal = global; const profileNames: string[] = []; -let instrumentationEnabled = false; - -export function enable() { - instrumentationEnabled = true; -} - -export function disable() { - instrumentationEnabled = false; -} - export const time = (global).__time || Date.now; export function start(name: string): void { let info = timers[name]; if (info) { - if (info.isRunning) { - throw new Error(`Timer already running: ${name}`); - } info.currentStart = time(); - info.isRunning = true; + info.runCount++; } else { info = { totalTime: 0, count: 0, currentStart: time(), - isRunning: true + runCount: 1 }; timers[name] = info; } } -export function pause(name: string): TimerInfo { - let info = pauseInternal(name); - return info; -} - export function stop(name: string): TimerInfo { - let info = pauseInternal(name); - console.log(`---- [${name}] STOP total: ${info.totalTime} count:${info.count}`); - - timers[name] = undefined; - return info; -} - -export function isRunning(name: string): boolean { - const info = timers[name]; - return !!(info && info.isRunning); -} - -function pauseInternal(name: string): TimerInfo { const info = timers[name]; if (!info) { throw new Error(`No timer started: ${name}`); } - if (info.isRunning) { - info.lastTime = time() - info.currentStart; - info.totalTime += info.lastTime; - info.count++; - info.currentStart = 0; - info.isRunning = false; + if (info.runCount) { + info.runCount--; + if (info.runCount) { + info.count++; + } else { + info.lastTime = time() - info.currentStart; + info.totalTime += info.lastTime; + info.count++; + info.currentStart = 0; + } + } else { + throw new Error(`Timer ${name} paused more times than started.`); } return info; } -function profileFunction(fn: F, customName?: string): F { - const name = customName || fn.name; +export function timer(name: string): TimerInfo { + return timers[name]; +} + +export function print(name: string): TimerInfo { + const info = timers[name]; + if (!info) { + throw new Error(`No timer started: ${name}`); + } + + console.log(`---- [${name}] STOP total: ${info.totalTime} count:${info.count}`); + return info; +} + +export function isRunning(name: string): boolean { + const info = timers[name]; + return !!(info && info.runCount); +} + +function countersProfileFunctionFactory(fn: F, name: string): F { profileNames.push(name); return function() { start(name); try { return fn.apply(this, arguments); } finally { - pause(name); + stop(name); } } } +function timelineProfileFunctionFactory(fn: F, name: string): F { + return function() { + const start = time(); + try { + return fn.apply(this, arguments); + } finally { + const end = time(); + console.log(`Timeline: Modules: ${name} (${start}ms. - ${end}ms.)`); + } + } +} + +let profileFunctionFactory: (fn: F, name: string) => F; +export function enable(mode: InstrumentationMode = "counters") { + profileFunctionFactory = mode && { + counters: countersProfileFunctionFactory, + timeline: timelineProfileFunctionFactory + }[mode]; +} + +try { + const appConfig = global.require("~/package.json"); + if (appConfig && appConfig.profiling) { + if (appConfig && appConfig.profiling) { + enable(appConfig.profiling); + } + } +} catch(e) { + console.log("Profiling startup failed to figure out defaults from package.json, error: " + e); +} + +export function disable() { + profileFunctionFactory = undefined; +} + +function profileFunction(fn: F, customName?: string): F { + return profileFunctionFactory(fn, customName || fn.name); +} + const profileMethodUnnamed = (target, key, descriptor) => { // save a reference to the original method this way we keep the values currently in the // descriptor and don't overwrite what another decorator might have done to the descriptor. @@ -114,17 +149,8 @@ const profileMethodUnnamed = (target, key, descriptor) => { let name = className + key; - profileNames.push(name); - //editing the descriptor/value parameter - descriptor.value = function () { - start(name); - try { - return originalMethod.apply(this, arguments); - } finally { - pause(name); - } - }; + descriptor.value = profileFunctionFactory(originalMethod, name); // return edited descriptor as opposed to overwriting the descriptor return descriptor; @@ -140,17 +166,8 @@ function profileMethodNamed(name: string): MethodDecorator { } var originalMethod = descriptor.value; - profileNames.push(name); - //editing the descriptor/value parameter - descriptor.value = function () { - start(name); - try { - return originalMethod.apply(this, arguments); - } finally { - pause(name); - } - }; + descriptor.value = profileFunctionFactory(originalMethod, name); // return edited descriptor as opposed to overwriting the descriptor return descriptor; @@ -163,27 +180,27 @@ const voidMethodDecorator = () => { export function profile(nameFnOrTarget?: string | Function | Object, fnOrKey?: Function | string | symbol, descriptor?: PropertyDescriptor): Function | MethodDecorator { if (typeof nameFnOrTarget === "object" && (typeof fnOrKey === "string" || typeof fnOrKey === "symbol")) { - if (!instrumentationEnabled) { + if (!profileFunctionFactory) { return; } return profileMethodUnnamed(nameFnOrTarget, fnOrKey, descriptor); } else if (typeof nameFnOrTarget === "string" && typeof fnOrKey === "function") { - if (!instrumentationEnabled) { + if (!profileFunctionFactory) { return fnOrKey; } return profileFunction(fnOrKey, nameFnOrTarget); } else if (typeof nameFnOrTarget === "function") { - if (!instrumentationEnabled) { + if (!profileFunctionFactory) { return nameFnOrTarget; } return profileFunction(nameFnOrTarget); } else if (typeof nameFnOrTarget === "string") { - if (!instrumentationEnabled) { + if (!profileFunctionFactory) { return voidMethodDecorator; } return profileMethodNamed(nameFnOrTarget); } else { - if (!instrumentationEnabled) { + if (!profileFunctionFactory) { return voidMethodDecorator; } return profileMethodUnnamed; @@ -193,7 +210,6 @@ export function profile(nameFnOrTarget?: string | Function | Object, fnOrKey?: F export function dumpProfiles(): void { profileNames.forEach(function (name) { const info = timers[name]; - if (info) { console.log("---- [" + name + "] STOP total: " + info.totalTime + " count:" + info.count); } @@ -206,12 +222,11 @@ export function dumpProfiles(): void { export function resetProfiles(): void { profileNames.forEach(function (name) { const info = timers[name]; - if (info) { - if (!info.isRunning) { - timers[name] = undefined; - } else { + if (info.runCount) { console.log("---- timer with name [" + name + "] is currently running and won't be reset"); + } else { + timers[name] = undefined; } } }); diff --git a/tns-core-modules/ui/button/button.android.ts b/tns-core-modules/ui/button/button.android.ts index 6c7fde2cb..9210e92cd 100644 --- a/tns-core-modules/ui/button/button.android.ts +++ b/tns-core-modules/ui/button/button.android.ts @@ -3,7 +3,7 @@ paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length, zIndexProperty, textAlignmentProperty, TextAlignment } from "./button-common"; - +import { profile } from "../../profiling"; import { TouchGestureEventData, GestureTypes, TouchAction } from "../gestures"; export * from "./button-common"; @@ -41,6 +41,7 @@ export class Button extends ButtonBase { private _highlightedHandler: (args: TouchGestureEventData) => void; + @profile public createNativeView() { initializeClickListener(); const button = new android.widget.Button(this._context); diff --git a/tns-core-modules/ui/core/properties/properties.ts b/tns-core-modules/ui/core/properties/properties.ts index 222e95e9e..668309a9e 100644 --- a/tns-core-modules/ui/core/properties/properties.ts +++ b/tns-core-modules/ui/core/properties/properties.ts @@ -6,6 +6,8 @@ import { ViewBase } from "../view-base"; import { WrappedValue, PropertyChangeData } from "../../../data/observable"; import { Style } from "../../styling/style"; +import { profile } from "../../../profiling"; + export { Style }; export const unsetValue: any = new Object(); @@ -964,7 +966,7 @@ function inheritableCssPropertyValuesOn(style: Style): Array<{ property: Inherit return array; } -export function initNativeView(view: ViewBase): void { +export const initNativeView = profile('"properties".initNativeView', function initNativeView(view: ViewBase): void { let symbols = Object.getOwnPropertySymbols(view); for (let symbol of symbols) { const property: Property = symbolPropertyMap[symbol]; @@ -1005,7 +1007,7 @@ export function initNativeView(view: ViewBase): void { view[property.setNative](value); } } -} +}); export function resetNativeView(view: ViewBase): void { let symbols = Object.getOwnPropertySymbols(view); diff --git a/tns-core-modules/ui/core/view-base/view-base.ts b/tns-core-modules/ui/core/view-base/view-base.ts index 276d52872..a889b621f 100644 --- a/tns-core-modules/ui/core/view-base/view-base.ts +++ b/tns-core-modules/ui/core/view-base/view-base.ts @@ -17,6 +17,8 @@ import * as types from "../../../utils/types"; import { Color } from "../../../color"; +import { profile } from "../../../profiling"; + export { isIOS, isAndroid, layout, Color }; export * from "../bindable"; export * from "../properties"; @@ -260,6 +262,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition this[name] = WrappedValue.unwrap(value); } + @profile public onLoaded() { this._isLoaded = true; this._loadEachChild(); @@ -273,6 +276,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition }); } + @profile public onUnloaded() { this._unloadEachChild(); this._isLoaded = false; @@ -299,6 +303,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition }); } + @profile public _applyStyleFromScope() { const scope = this._styleScope; if (scope) { @@ -309,6 +314,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition } // TODO: Make sure the state is set to null and this is called on unloaded to clean up change listeners... + @profile _setCssState(next: ssm.CssState): void { const previous = this._cssState; this._cssState = next; @@ -380,6 +386,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition private _invalidateCssHandler; private _invalidateCssHandlerSuspended: boolean; + @profile private applyCssState(): void { this._batchUpdate(() => { if (!this._cssState) { @@ -412,6 +419,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition return allStates; } + @profile public addPseudoClass(name: string): void { let allStates = this.getAllAliasedStates(name); for (let i = 0; i < allStates.length; i++) { @@ -422,6 +430,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition } } + @profile public deletePseudoClass(name: string): void { let allStates = this.getAllAliasedStates(name); for (let i = 0; i < allStates.length; i++) { @@ -432,6 +441,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition } } + @profile private _applyInlineStyle(inlineStyle) { if (typeof inlineStyle === "string") { try { @@ -504,6 +514,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition } } + @profile public requestLayout(): void { let parent = this.parent; if (parent) { @@ -515,6 +526,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition // } + @profile public _addView(view: ViewBase, atIndex?: number) { if (traceEnabled()) { traceWrite(`${this}._addView(${view}, ${atIndex})`, traceCategories.ViewHierarchy); @@ -535,6 +547,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition view._parentChanged(null); } + @profile private _setStyleScope(scope: ssm.StyleScope): void { this._styleScope = scope; this._applyStyleFromScope(); @@ -623,6 +636,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition } } + @profile public _setupUI(context: android.content.Context, atIndex?: number, parentIsLoaded?: boolean) { traceNotifyEvent(this, "_setupUI"); if (traceEnabled()) { @@ -701,6 +715,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition }); } + @profile public _tearDownUI(force?: boolean) { // No context means we are already teared down. if (!this._context) { diff --git a/tns-core-modules/ui/core/view/view.android.ts b/tns-core-modules/ui/core/view/view.android.ts index 07b295a1c..118e2a4c5 100644 --- a/tns-core-modules/ui/core/view/view.android.ts +++ b/tns-core-modules/ui/core/view/view.android.ts @@ -17,6 +17,7 @@ import { rotateProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty } from "../../styling/style-properties"; +import { profile } from "../../../profiling"; export * from "./view-common"; @@ -82,11 +83,13 @@ export class View extends ViewCommon { } } + @profile public onLoaded() { super.onLoaded(); this.setOnTouchListener(); } + @profile public onUnloaded() { if (this.touchListenerIsSet) { this.nativeView.setOnTouchListener(null); @@ -132,6 +135,7 @@ export class View extends ViewCommon { } } + @profile public requestLayout(): void { super.requestLayout(); if (this.nativeView) { diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 5585b1dc2..0fdf50cec 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -6,7 +6,6 @@ import { ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, traceEnabled, traceWrite, traceCategories } from "./view-common"; - import { Visibility, visibilityProperty, opacityProperty, @@ -14,6 +13,7 @@ import { translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty, clipPathProperty } from "../../styling/style-properties"; +import { profile } from "../../../profiling"; export * from "./view-common"; @@ -80,6 +80,7 @@ export class View extends ViewCommon { } } + @profile public layout(left: number, top: number, right: number, bottom: number): void { let { boundsChanged, sizeChanged } = this._setCurrentLayoutBounds(left, top, right, bottom); this.layoutNativeView(left, top, right, bottom); @@ -100,6 +101,7 @@ export class View extends ViewCommon { this._privateFlags |= PFLAG_MEASURED_DIMENSION_SET; } + @profile public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { const view = this.nativeView; const width = layout.getMeasureSpecSize(widthMeasureSpec); diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 4fddad67a..6b8cd741f 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -10,6 +10,8 @@ import { FrameBase, application, NavigationContext, stack, goBack, View, Observa import { DIALOG_FRAGMENT_TAG } from "../page/constants"; import * as transitionModule from "../transition"; +import { profile } from "../../profiling"; + export * from "./frame-common"; const HIDDEN = "_hidden"; @@ -117,6 +119,7 @@ export class Frame extends FrameBase { return this._android; } + @profile public _navigateCore(backstackEntry: BackstackEntry) { super._navigateCore(backstackEntry); @@ -631,6 +634,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { public entry: BackstackEntry; public clearHistory: boolean; + @profile public onHiddenChanged(fragment: android.app.Fragment, hidden: boolean, superFunc: Function): void { if (traceEnabled()) { traceWrite(`${fragment}.onHiddenChanged(${hidden})`, traceCategories.NativeLifecycle); @@ -644,6 +648,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { } } + @profile public onCreateAnimator(fragment: android.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator { let nextAnimString: string; switch (nextAnim) { @@ -665,6 +670,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { return animator; } + @profile public onCreate(fragment: android.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void { if (traceEnabled()) { traceWrite(`${fragment}.onCreate(${savedInstanceState})`, traceCategories.NativeLifecycle); @@ -687,6 +693,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { } } + @profile public onCreateView(fragment: android.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View { if (traceEnabled()) { traceWrite(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, traceCategories.NativeLifecycle); @@ -709,6 +716,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { return page.nativeView; } + @profile public onSaveInstanceState(fragment: android.app.Fragment, outState: android.os.Bundle, superFunc: Function): void { if (traceEnabled()) { traceWrite(`${fragment}.onSaveInstanceState(${outState})`, traceCategories.NativeLifecycle); @@ -719,6 +727,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { } } + @profile public onDestroyView(fragment: android.app.Fragment, superFunc: Function): void { if (traceEnabled()) { traceWrite(`${fragment}.onDestroyView()`, traceCategories.NativeLifecycle); @@ -728,6 +737,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { onFragmentHidden(fragment, true); } + @profile public onDestroy(fragment: android.app.Fragment, superFunc: Function): void { if (traceEnabled()) { traceWrite(`${fragment}.onDestroy()`, traceCategories.NativeLifecycle); @@ -735,6 +745,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { superFunc.call(fragment); } + @profile public toStringOverride(fragment: android.app.Fragment, superFunc: Function): string { return `${fragment.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`; } @@ -743,6 +754,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { class ActivityCallbacksImplementation implements AndroidActivityCallbacks { private _rootView: View; + @profile public onCreate(activity: android.app.Activity, savedInstanceState: android.os.Bundle, superFunc: Function): void { if (traceEnabled()) { traceWrite(`Activity.onCreate(${savedInstanceState})`, traceCategories.NativeLifecycle); @@ -812,6 +824,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { activityInitialized = true; } + @profile public onSaveInstanceState(activity: android.app.Activity, outState: android.os.Bundle, superFunc: Function): void { superFunc.call(activity, outState); let view = this._rootView; @@ -820,6 +833,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { } } + @profile public onStart(activity: any, superFunc: Function): void { superFunc.call(activity); @@ -832,6 +846,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { } } + @profile public onStop(activity: any, superFunc: Function): void { superFunc.call(activity); @@ -844,6 +859,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { } } + @profile public onDestroy(activity: any, superFunc: Function): void { let rootView = this._rootView; if (rootView && rootView._context) { @@ -860,6 +876,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { application.notify(exitArgs); } + @profile public onBackPressed(activity: any, superFunc: Function): void { if (traceEnabled()) { traceWrite("NativeScriptActivity.onBackPressed;", traceCategories.NativeLifecycle); @@ -882,6 +899,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { } } + @profile public onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array, grantResults: Array, superFunc: Function): void { if (traceEnabled()) { traceWrite("NativeScriptActivity.onRequestPermissionsResult;", traceCategories.NativeLifecycle); @@ -897,6 +915,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { }); } + @profile public onActivityResult(activity: any, requestCode: number, resultCode: number, data: android.content.Intent, superFunc: Function): void { superFunc.call(activity, requestCode, resultCode, data); if (traceEnabled()) { diff --git a/tns-core-modules/ui/frame/frame.ios.ts b/tns-core-modules/ui/frame/frame.ios.ts index 749b9c6ca..bb64972cc 100644 --- a/tns-core-modules/ui/frame/frame.ios.ts +++ b/tns-core-modules/ui/frame/frame.ios.ts @@ -1,6 +1,7 @@ // Definitions. import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition } from "."; import { Page } from "../page"; +import { profile } from "../../profiling"; //Types. import { FrameBase, View, application, layout, traceEnabled, traceWrite, traceCategories, isCategorySet } from "./frame-common"; @@ -59,6 +60,7 @@ export class Frame extends FrameBase { }); } + @profile public onLoaded() { super.onLoaded(); @@ -78,6 +80,7 @@ export class Frame extends FrameBase { } } + @profile public _navigateCore(backstackEntry: BackstackEntry) { super._navigateCore(backstackEntry); @@ -491,6 +494,7 @@ class UINavigationControllerImpl extends UINavigationController { return this._owner.get(); } + @profile public viewDidLoad(): void { super.viewDidLoad(); let owner = this._owner.get(); diff --git a/tns-core-modules/ui/label/label.android.ts b/tns-core-modules/ui/label/label.android.ts index 0623bcc2d..92decb8ad 100644 --- a/tns-core-modules/ui/label/label.android.ts +++ b/tns-core-modules/ui/label/label.android.ts @@ -1,5 +1,6 @@ import { Label as LabelDefinition } from "."; import { TextBase, WhiteSpace, whiteSpaceProperty } from "../text-base"; +import { profile } from "../../profiling"; export * from "../text-base"; @@ -15,6 +16,7 @@ export class Label extends TextBase implements LabelDefinition { this.style.whiteSpace = value ? "normal" : "nowrap"; } + @profile public createNativeView() { if (!TextView) { TextView = android.widget.TextView; diff --git a/tns-core-modules/ui/list-picker/list-picker.ios.ts b/tns-core-modules/ui/list-picker/list-picker.ios.ts index 7b34fe3fa..5ad50505f 100644 --- a/tns-core-modules/ui/list-picker/list-picker.ios.ts +++ b/tns-core-modules/ui/list-picker/list-picker.ios.ts @@ -1,5 +1,6 @@ import { ListPickerBase, Color, selectedIndexProperty, itemsProperty, backgroundColorProperty, colorProperty } from "./list-picker-common"; import { ItemsSource } from "."; +import { profile } from "../../profiling"; export * from "./list-picker-common"; @@ -18,6 +19,7 @@ export class ListPicker extends ListPickerBase { this.nativeView = this._ios; } + @profile public onLoaded() { super.onLoaded(); this._ios.delegate = this._delegate; diff --git a/tns-core-modules/ui/list-view/list-view.android.ts b/tns-core-modules/ui/list-view/list-view.android.ts index 1cf00023b..185820306 100644 --- a/tns-core-modules/ui/list-view/list-view.android.ts +++ b/tns-core-modules/ui/list-view/list-view.android.ts @@ -6,6 +6,7 @@ import { import { StackLayout } from "../layouts/stack-layout"; import { ProxyViewContainer } from "../proxy-view-container"; import { LayoutBase } from "../layouts/layout-base"; +import { profile } from "../../profiling"; export * from "./list-view-common"; @@ -49,6 +50,7 @@ export class ListView extends ListViewBase { public _realizedItems = new Map(); public _realizedTemplates = new Map>(); + @profile public createNativeView() { initializeItemClickListener(); @@ -238,6 +240,7 @@ function ensureListViewAdapterClass() { return itemViewType; } + @profile public getView(index: number, convertView: android.view.View, parent: android.view.ViewGroup): android.view.View { //this.owner._dumpRealizedTemplates(); diff --git a/tns-core-modules/ui/list-view/list-view.ios.ts b/tns-core-modules/ui/list-view/list-view.ios.ts index 1b3ad4e0f..8d8de1319 100644 --- a/tns-core-modules/ui/list-view/list-view.ios.ts +++ b/tns-core-modules/ui/list-view/list-view.ios.ts @@ -6,6 +6,7 @@ import { import { StackLayout } from "../layouts/stack-layout"; import { ProxyViewContainer } from "../proxy-view-container"; import { ios } from "../../utils/utils"; +import { profile } from "../../profiling"; export * from "./list-view-common"; @@ -225,6 +226,7 @@ export class ListView extends ListViewBase { this._ios.clipsToBounds = true; } + @profile public onLoaded() { super.onLoaded(); if (this._isDataDirty) { diff --git a/tns-core-modules/ui/page/page-common.ts b/tns-core-modules/ui/page/page-common.ts index c18da4cc6..dc01ac3e3 100644 --- a/tns-core-modules/ui/page/page-common.ts +++ b/tns-core-modules/ui/page/page-common.ts @@ -8,6 +8,7 @@ import { ActionBar } from "../action-bar"; import { KeyframeAnimationInfo } from "../animation/keyframe-animation"; import { StyleScope } from "../styling/style-scope"; import { File, path, knownFolders } from "../../file-system"; +import { profile } from "../../profiling"; export * from "../content-view"; @@ -93,6 +94,7 @@ export class PageBase extends ContentView implements PageDefinition { return this; } + @profile public onLoaded(): void { this._refreshCss(); super.onLoaded(); diff --git a/tns-core-modules/ui/page/page.android.ts b/tns-core-modules/ui/page/page.android.ts index 4bc38f684..114af62d5 100644 --- a/tns-core-modules/ui/page/page.android.ts +++ b/tns-core-modules/ui/page/page.android.ts @@ -3,6 +3,7 @@ import { ActionBar } from "../action-bar"; import { GridLayout } from "../layouts/grid-layout"; import { DIALOG_FRAGMENT_TAG } from "./constants"; import { device } from "../../platform"; +import { profile } from "../../profiling"; export * from "./page-common"; @@ -118,6 +119,7 @@ export class Page extends PageBase { return super._addViewToNativeVisualTree(child, atIndex); } + @profile public onLoaded() { super.onLoaded(); if (this.actionBarHidden !== undefined) { diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts index 1358372a0..25347ac7f 100644 --- a/tns-core-modules/ui/page/page.ios.ts +++ b/tns-core-modules/ui/page/page.ios.ts @@ -8,6 +8,7 @@ import { device } from "../../platform"; // HACK: Webpack. Use a fully-qualified import to allow resolve.extensions(.ios.js) to // kick in. `../utils` doesn't seem to trigger the webpack extensions mechanism. import * as uiUtils from "tns-core-modules/ui/utils"; +import { profile } from "../../profiling"; export * from "./page-common"; @@ -354,6 +355,7 @@ export class Page extends PageBase { this._addNativeView(newView); } + @profile public onLoaded() { // loaded/unloaded events are handled in page viewWillAppear/viewDidDisappear if (this._enableLoadedEvents) { diff --git a/tns-core-modules/ui/repeater/repeater.ts b/tns-core-modules/ui/repeater/repeater.ts index a04fd5ec4..b04bed927 100644 --- a/tns-core-modules/ui/repeater/repeater.ts +++ b/tns-core-modules/ui/repeater/repeater.ts @@ -5,6 +5,7 @@ import { StackLayout } from "../layouts/stack-layout"; import { ObservableArray, ChangedData } from "../../data/observable-array"; import { addWeakEventListener, removeWeakEventListener } from "../core/weak-event-listener"; import { parse } from "../builder"; +import { profile } from "../../profiling"; export * from "../layouts/layout-base"; @@ -27,6 +28,7 @@ export class Repeater extends CustomLayoutView implements RepeaterDefinition { public itemTemplate: string | Template; public itemsLayout: LayoutBase; + @profile public onLoaded() { if (this._isDirty) { this.refresh(); diff --git a/tns-core-modules/ui/scroll-view/scroll-view-common.ts b/tns-core-modules/ui/scroll-view/scroll-view-common.ts index 91a8af07f..f0dabf7ac 100644 --- a/tns-core-modules/ui/scroll-view/scroll-view-common.ts +++ b/tns-core-modules/ui/scroll-view/scroll-view-common.ts @@ -1,5 +1,6 @@ import { ScrollView as ScrollViewDefinition, Orientation } from "."; import { ContentView, Property, makeParser, makeValidator } from "../content-view"; +import { profile } from "../../profiling"; export * from "../content-view"; @@ -27,6 +28,7 @@ export abstract class ScrollViewBase extends ContentView implements ScrollViewDe } } + @profile public onLoaded() { super.onLoaded(); diff --git a/tns-core-modules/ui/tab-view/tab-view.ios.ts b/tns-core-modules/ui/tab-view/tab-view.ios.ts index e6ed146c1..eb82f8dde 100644 --- a/tns-core-modules/ui/tab-view/tab-view.ios.ts +++ b/tns-core-modules/ui/tab-view/tab-view.ios.ts @@ -5,10 +5,10 @@ import { tabTextColorProperty, tabBackgroundColorProperty, selectedTabTextColorProperty, iosIconRenderingModeProperty, View, fontInternalProperty, layout, traceEnabled, traceWrite, traceCategories, Color, initNativeView } from "./tab-view-common" - import { textTransformProperty, TextTransform, getTransformedText } from "../text-base"; import { fromFileOrResource } from "../../image-source"; import { Page } from "../page"; +import { profile } from "../../profiling"; export * from "./tab-view-common"; @@ -184,6 +184,7 @@ export class TabView extends TabViewBase { //This delegate is set on the last line of _addTabs method. } + @profile public onLoaded() { super.onLoaded(); this._ios.delegate = this._delegate; diff --git a/tns-core-modules/ui/text-field/text-field.ios.ts b/tns-core-modules/ui/text-field/text-field.ios.ts index af274ecc2..eb7ada5fe 100644 --- a/tns-core-modules/ui/text-field/text-field.ios.ts +++ b/tns-core-modules/ui/text-field/text-field.ios.ts @@ -2,6 +2,7 @@ TextFieldBase, secureProperty, textProperty, hintProperty, colorProperty, placeholderColorProperty, Length, paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty, _updateCharactersInRangeReplacementString, Color, layout } from "./text-field-common"; +import { profile } from "../../profiling"; export * from "./text-field-common"; @@ -145,6 +146,7 @@ export class TextField extends TextFieldBase { this.nativeView = this._ios; } + @profile public onLoaded() { super.onLoaded(); this._ios.delegate = this._delegate; diff --git a/tns-core-modules/ui/text-view/text-view.ios.ts b/tns-core-modules/ui/text-view/text-view.ios.ts index 6adf10f6f..6c008620f 100644 --- a/tns-core-modules/ui/text-view/text-view.ios.ts +++ b/tns-core-modules/ui/text-view/text-view.ios.ts @@ -5,6 +5,7 @@ import { paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty, Length, _updateCharactersInRangeReplacementString, Color, layout } from "../editable-text-base"; +import { profile } from "../../profiling"; export * from "../editable-text-base"; @@ -92,6 +93,7 @@ export class TextView extends EditableTextBase implements TextViewDefinition { this._delegate = UITextViewDelegateImpl.initWithOwner(new WeakRef(this)); } + @profile public onLoaded() { super.onLoaded(); this._ios.delegate = this._delegate; diff --git a/tns-core-modules/ui/web-view/web-view.ios.ts b/tns-core-modules/ui/web-view/web-view.ios.ts index aa90dcaef..3ddbc45e8 100644 --- a/tns-core-modules/ui/web-view/web-view.ios.ts +++ b/tns-core-modules/ui/web-view/web-view.ios.ts @@ -1,4 +1,5 @@ import { WebViewBase, knownFolders, traceWrite, traceEnabled, traceCategories, NavigationType } from "./web-view-common"; +import { profile } from "../../profiling"; export * from "./web-view-common"; @@ -91,6 +92,7 @@ export class WebView extends WebViewBase { this._delegate = UIWebViewDelegateImpl.initWithOwner(new WeakRef(this)); } + @profile public onLoaded() { super.onLoaded(); this._ios.delegate = this._delegate;