diff --git a/doric-iOS/Pod/Classes/DoricContext.h b/doric-iOS/Pod/Classes/DoricContext.h index 92235616..a21302d6 100644 --- a/doric-iOS/Pod/Classes/DoricContext.h +++ b/doric-iOS/Pod/Classes/DoricContext.h @@ -24,6 +24,7 @@ #import "DoricDriverProtocol.h" #import "DoricNavigatorDelegate.h" #import "DoricNavBarDelegate.h" +#import "DoricPerformanceProfile.h" NS_ASSUME_NONNULL_BEGIN @@ -44,6 +45,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, strong) NSMutableDictionary *> *headNodes; @property(nonatomic, copy) NSString *extra; @property(nonatomic, assign) BOOL destroyed; +@property(nonatomic, strong) DoricPerformanceProfile *performanceProfile; - (instancetype)initWithScript:(NSString *)script source:(NSString *)source extra:(NSString *)extra; diff --git a/doric-iOS/Pod/Classes/DoricContextManager.m b/doric-iOS/Pod/Classes/DoricContextManager.m index 1a23d2ad..f9611dde 100644 --- a/doric-iOS/Pod/Classes/DoricContextManager.m +++ b/doric-iOS/Pod/Classes/DoricContextManager.m @@ -53,6 +53,7 @@ + (instancetype)instance { - (void)createContext:(DoricContext *)context script:(NSString *)script source:(NSString *)source { context.contextId = [NSString stringWithFormat:@"%ld", (long) ++self.counter]; + context.performanceProfile = [[DoricPerformanceProfile alloc] initWithName:context.contextId]; dispatch_sync(self.mapQueue, ^() { [self.contextMapTable setObject:context forKey:context.contextId]; }); diff --git a/doric-iOS/Pod/Classes/DoricNativeDriver.m b/doric-iOS/Pod/Classes/DoricNativeDriver.m index c0a1ba7a..3099fb5e 100644 --- a/doric-iOS/Pod/Classes/DoricNativeDriver.m +++ b/doric-iOS/Pod/Classes/DoricNativeDriver.m @@ -24,6 +24,7 @@ #import "DoricJSEngine.h" #import "DoricConstant.h" #import "DoricContextManager.h" +#import "DoricPerformanceProfile.h" @interface DoricNativeDriver () @property(nonatomic, strong) DoricJSEngine *jsExecutor; @@ -109,20 +110,28 @@ - (DoricAsyncResult *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArr - (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method arguments:(va_list)args { DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; NSMutableArray *array = [[NSMutableArray alloc] init]; + NSMutableArray *printedArgs = [[NSMutableArray alloc] init]; [array addObject:contextId]; [array addObject:method]; + [printedArgs addObject:method]; id arg = va_arg(args, id); while (arg != nil) { [array addObject:arg]; + [printedArgs addObject:arg]; arg = va_arg(args, JSValue *); } + DoricPerformanceProfile *performanceProfile = [DoricContextManager.instance getContext:contextId].performanceProfile; + NSString *anchorName = [NSString stringWithFormat:@"call:%@", [printedArgs componentsJoinedByString:@","]]; + [performanceProfile prepare:anchorName]; __weak typeof(self) _self = self; [self.jsExecutor ensureRunOnJSThread:^{ __strong typeof(_self) self = _self; if (!self) return; @try { + [performanceProfile start:anchorName]; JSValue *jsValue = [self.jsExecutor invokeDoricMethod:DORIC_CONTEXT_INVOKE argumentsArray:array]; [ret setupResult:jsValue]; + [performanceProfile end:anchorName]; } @catch (NSException *exception) { [ret setupError:exception]; [self.jsExecutor.registry onException:exception inContext:[[DoricContextManager instance] getContext:contextId]]; @@ -139,13 +148,18 @@ - (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString for (id arg in args) { [array addObject:arg]; } + DoricPerformanceProfile *performanceProfile = [DoricContextManager.instance getContext:contextId].performanceProfile; + NSString *anchorName = [NSString stringWithFormat:@"call:%@,%@", method, [args componentsJoinedByString:@","]]; + [performanceProfile prepare:anchorName]; __weak typeof(self) _self = self; [self.jsExecutor ensureRunOnJSThread:^{ __strong typeof(_self) self = _self; if (!self) return; @try { + [performanceProfile start:anchorName]; JSValue *jsValue = [self.jsExecutor invokeDoricMethod:DORIC_CONTEXT_INVOKE argumentsArray:array]; [ret setupResult:jsValue]; + [performanceProfile end:anchorName]; } @catch (NSException *exception) { [ret setupError:exception]; [self.jsExecutor.registry onException:exception inContext:[[DoricContextManager instance] getContext:contextId]]; @@ -156,13 +170,17 @@ - (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString - (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source { DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + DoricPerformanceProfile *performanceProfile = [DoricContextManager.instance getContext:contextId].performanceProfile; + [performanceProfile prepare:@"Create"]; __weak typeof(self) _self = self; [self.jsExecutor ensureRunOnJSThread:^{ __strong typeof(_self) self = _self; if (!self) return; @try { + [performanceProfile start:@"Create"]; [self.jsExecutor prepareContext:contextId script:script source:source]; [ret setupResult:@YES]; + [performanceProfile end:@"Create"]; } @catch (NSException *exception) { [ret setupError:exception]; [self.registry onException:exception inContext:[[DoricContextManager instance] getContext:contextId]]; @@ -173,13 +191,17 @@ - (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)scr - (DoricAsyncResult *)destroyContext:(NSString *)contextId { DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + DoricPerformanceProfile *performanceProfile = [DoricContextManager.instance getContext:contextId].performanceProfile; + [performanceProfile prepare:@"Destroy"]; __weak typeof(self) _self = self; [self.jsExecutor ensureRunOnJSThread:^{ __strong typeof(_self) self = _self; if (!self) return; @try { + [performanceProfile start:@"Destroy"]; [self.jsExecutor destroyContext:contextId]; [ret setupResult:@YES]; + [performanceProfile end:@"Destroy"]; } @catch (NSException *exception) { [ret setupError:exception]; [self.jsExecutor.registry onException:exception inContext:[[DoricContextManager instance] getContext:contextId]]; diff --git a/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m b/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m index 9c2c1089..b51a29f7 100644 --- a/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m +++ b/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m @@ -28,6 +28,7 @@ #import #import "DoricContext.h" #import "DoricContextManager.h" +#import "DoricPerformanceProfile.h" @interface DoricDefaultMonitor : NSObject @end @@ -49,6 +50,7 @@ @interface DoricJSEngine () @property(nonatomic, strong) NSThread *jsThread; @property(nonatomic, assign) BOOL destroyed; @property(nonatomic, assign) BOOL initialized; +@property(nonatomic, strong) DoricPerformanceProfile *profile; @end @implementation DoricJSEngine @@ -56,6 +58,8 @@ @implementation DoricJSEngine - (instancetype)init { if (self = [super init]) { _initialized = NO; + _profile = [[DoricPerformanceProfile alloc] initWithName:@"JSEngine"]; + [_profile prepare:@"Init"]; _jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; [_jsThread start]; NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; @@ -93,6 +97,7 @@ - (instancetype)init { }.mutableCopy; self.registry = [[DoricRegistry alloc] initWithJSEngine:self]; [self ensureRunOnJSThread:^() { + [_profile start:@"Init"]; self.timers = [[NSMutableDictionary alloc] init]; self.bridgeExtension = [DoricBridgeExtension new]; self.bridgeExtension.registry = self.registry; @@ -100,6 +105,7 @@ - (instancetype)init { [self initJSExecutor]; [self initDoricEnvironment]; self.initialized = YES; + [_profile end:@"Init"]; }]; [self.registry registerMonitor:[DoricDefaultMonitor new]]; } diff --git a/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h new file mode 100644 index 00000000..db14fa1c --- /dev/null +++ b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h @@ -0,0 +1,37 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricPerformanceProfile.h +// DoricCore +// +// Created by pengfei.zhou on 2021/3/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricPerformanceProfile : NSObject +- (instancetype)initWithName:(NSString *)name; + +- (void)prepare:(NSString *)anchorName; + +- (void)start:(NSString *)anchorName; + +- (void)end:(NSString *)anchorName; +@end + +NS_ASSUME_NONNULL_END diff --git a/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m new file mode 100644 index 00000000..23326fe9 --- /dev/null +++ b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m @@ -0,0 +1,103 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricPerformanceProfile.m +// DoricCore +// +// Created by pengfei.zhou on 2021/3/29. +// + +#import "DoricPerformanceProfile.h" + +const bool _DEBUG = YES; + +@interface DoricPerformanceProfile () +@property(nonatomic, copy) NSString *name; +@property(nonatomic, strong) NSMutableDictionary *anchorMap; +@property(nonatomic, strong) dispatch_queue_t anchorQueue; +@end + +@implementation DoricPerformanceProfile +- (instancetype)initWithName:(NSString *)name { + if (self = [super init]) { + _name = name; + _anchorQueue = dispatch_queue_create("doric.performance.profile", DISPATCH_QUEUE_SERIAL); + _anchorMap = [NSMutableDictionary new]; + } + return self; +} + +- (NSString *)getPrepareAnchor:(NSString *)anchorName { + return [NSString stringWithFormat:@"%@#prepare", anchorName]; +} + +- (NSString *)getStartAnchor:(NSString *)anchorName { + return [NSString stringWithFormat:@"%@#start", anchorName]; +} + +- (NSString *)getEndAnchor:(NSString *)anchorName { + return [NSString stringWithFormat:@"%@#end", anchorName]; +} + +- (void)markAnchor:(NSString *)eventName { + if (!_DEBUG) { + return; + } + NSUInteger time = (NSUInteger) ([[NSDate date] timeIntervalSince1970] * 1000); + dispatch_async(self.anchorQueue, ^{ + self.anchorMap[eventName] = @(time); + }); +} + + +- (void)prepare:(NSString *)anchorName { + [self markAnchor:[self getPrepareAnchor:anchorName]]; +} + +- (void)start:(NSString *)anchorName { + [self markAnchor:[self getStartAnchor:anchorName]]; +} + +- (void)end:(NSString *)anchorName { + [self markAnchor:[self getEndAnchor:anchorName]]; + if (_DEBUG) { + [self print:anchorName]; + } +} + +- (void)print:(NSString *)anchorName { + dispatch_async(self.anchorQueue, ^{ + NSNumber *prepare = self.anchorMap[[self getPrepareAnchor:anchorName]]; + NSNumber *start = self.anchorMap[[self getStartAnchor:anchorName]]; + NSNumber *end = self.anchorMap[[self getEndAnchor:anchorName]]; + if (!end) { + end = @( (NSUInteger) [[NSDate date] timeIntervalSince1970] * 1000); + } + if (!start) { + start = end; + } + if (!prepare) { + prepare = start; + } + NSLog(@"[DoricPerformanceProfile] %@: %@ prepared %@ms, cost %@ms", + self.name, + anchorName, + @(start.integerValue - prepare.integerValue), + @(end.integerValue - start.integerValue) + ); + }); +} +@end diff --git a/doric-iOS/Pod/Classes/Plugin/DoricShaderPlugin.m b/doric-iOS/Pod/Classes/Plugin/DoricShaderPlugin.m index b06a59c2..6adcdedd 100644 --- a/doric-iOS/Pod/Classes/Plugin/DoricShaderPlugin.m +++ b/doric-iOS/Pod/Classes/Plugin/DoricShaderPlugin.m @@ -33,12 +33,14 @@ - (void)render:(NSDictionary *)argument withPromise:(DoricPromise *)promise { if (!argument) { return; } + [self.doricContext.performanceProfile prepare:@"Render"]; __weak typeof(self) _self = self; [self.doricContext dispatchToMainQueue:^{ __strong typeof(_self) self = _self; if (self.doricContext == nil) { return; } + [self.doricContext.performanceProfile start:@"Render"]; NSString *viewId = [argument optString:@"id"]; if (self.doricContext.rootNode.viewId == nil && [@"Root" isEqualToString:[argument optString:@"type"]]) { @@ -51,6 +53,7 @@ - (void)render:(NSDictionary *)argument withPromise:(DoricPromise *)promise { [viewNode requestLayout]; } [promise resolve:nil]; + [self.doricContext.performanceProfile end:@"Render"]; }]; }