From 46c6cde947b2bbfef30939e52eefa65993bbcf06 Mon Sep 17 00:00:00 2001 From: Harrison Harnisch Date: Fri, 21 Aug 2015 00:57:43 -0700 Subject: [PATCH] UI CPU and memory utilization graphs in Chrome debugging mode Summary: Chrome debugging UI is currently only showing connection state and logs in the console, leaving room for plenty of interesting information. I've pushed the UI (using the same convention set by FPS -- UI/JS) CPU and memory utilization data over the debug Websocket and tapped into the existing stream of JS calls that get ran in V8. The number of JS calls in a time interval is counted for all sub calls in a batch https://github.com/hharnisc/react-native/blob/master/packager/debugger.html#L150 The last 5 batches of JS calls are displayed in a list format. screen shot 2015-07-19 at 7 34 00 pm Charts are created with [Chart.JS](https://github.com/nnnick/Chart.js) (MIT licensed). Closes https://github.com/facebook/react-native/pull/2050 Github Author: Harrison Harnisch --- Libraries/WebSocket/RCTWebSocketExecutor.m | 34 +- React/Base/RCTProfile.h | 22 +- React/Base/RCTProfile.m | 70 +- packager/debugger.html | 302 +- packager/packager.js | 7 + packager/static/Chart.min.js | 11 + packager/static/JSXTransformer-0.13.3.js | 15919 +++++++++++++++++++ packager/static/react-0.13.3.min.js | 16 + 8 files changed, 16366 insertions(+), 15 deletions(-) create mode 100644 packager/static/Chart.min.js create mode 100644 packager/static/JSXTransformer-0.13.3.js create mode 100644 packager/static/react-0.13.3.min.js diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index bd9600d3c..22a63e83b 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -18,6 +18,7 @@ #import "RCTSparseArray.h" #import "RCTUtils.h" #import "RCTSRWebSocket.h" +#import "RCTProfile.h" typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); @@ -109,11 +110,19 @@ RCT_EXPORT_MODULE() - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message { NSError *error = nil; - NSDictionary *reply = RCTJSONParse(message, &error); - NSNumber *messageID = reply[@"replyID"]; - RCTWSMessageCallback callback = _callbacks[messageID]; - if (callback) { - callback(error, reply); + NSDictionary *parsedMessage = RCTJSONParse(message, &error); + + if ([parsedMessage objectForKey:@"method"]) { + NSString *methodName = parsedMessage[@"method"]; + if ([methodName isEqual:@"requestMetrics"]) { + [self sendUsageMetrics]; + } + } else if ([parsedMessage objectForKey:@"replyID"]) { + NSNumber *messageID = parsedMessage[@"replyID"]; + RCTWSMessageCallback callback = _callbacks[messageID]; + if (callback) { + callback(error, parsedMessage); + } } } @@ -181,6 +190,21 @@ RCT_EXPORT_MODULE() }]; } +- (void)sendUsageMetrics +{ + NSDictionary *memoryUsage = RCTProfileGetMemoryUsage(YES); + NSNumber *cpuUsage = RCTProfileGetCPUUsage(); + + NSDictionary *message = @{ + @"method": @"usageMetrics", + @"memoryUsage": memoryUsage, + @"deviceCPUUsage": cpuUsage + }; + + // TODO: handle errors + [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {}]; +} + - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { dispatch_async(_jsQueue, ^{ diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 794f4a910..96ede77db 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -73,7 +73,24 @@ RCT_EXTERN void RCTProfileEndEvent(uint64_t tag, NSDictionary *args); /** - * Collects the initial event information for the event and returns a reference ID + * Exposes memory usage metrics + */ + +NSDictionary *RCTProfileGetMemoryUsage(BOOL); + +/** + * Exposes device cpu usage metrics - Note this does not include JS Runtime CPU usage + */ + +NSNumber *RCTProfileGetCPUUsage(void); + +/** + * This pair of macros implicitly handle the event ID when beginning and ending + * an event, for both simplicity and performance reasons, this method is preferred + * + * NOTE: The EndEvent call has to be either, in the same scope of BeginEvent, + * or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible + * for EndEvent, in this case you may want to use the actual C functions. */ RCT_EXTERN int RCTProfileBeginAsyncEvent(uint64_t tag, NSString *name, @@ -139,6 +156,9 @@ RCT_EXTERN void RCTProfileUnhookModules(RCTBridge *); #define RCTProfileImmediateEvent(...) +#define RCTProfileGetMemoryUsage(...) +#define RCTProfileGetCPUUsage(...) + #define RCTProfileBlock(block, ...) block #define RCTProfileHookModules(...) diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 85d5a799a..4e21eae2f 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -72,7 +72,7 @@ static NSString *RCTProfileMemory(vm_size_t memory) return [NSString stringWithFormat:@"%.2lfmb", mem]; } -static NSDictionary *RCTProfileGetMemoryUsage(void) +NSDictionary *RCTProfileGetMemoryUsage(BOOL raw) { struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); @@ -81,14 +81,76 @@ static NSDictionary *RCTProfileGetMemoryUsage(void) (task_info_t)&info, &size); if( kerr == KERN_SUCCESS ) { + vm_size_t vs = info.virtual_size; + vm_size_t rs = info.resident_size; return @{ @"suspend_count": @(info.suspend_count), - @"virtual_size": RCTProfileMemory(info.virtual_size), - @"resident_size": RCTProfileMemory(info.resident_size), + @"virtual_size": raw ? @(vs) : RCTProfileMemory(vs), + @"resident_size": raw ? @(rs) : RCTProfileMemory(rs), }; } else { return @{}; } + +} + +NSNumber *RCTProfileGetCPUUsage(void) +{ + kern_return_t kr; + task_info_data_t tinfo; + mach_msg_type_number_t task_info_count; + + task_info_count = TASK_INFO_MAX; + kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); + if (kr != KERN_SUCCESS) { + return nil; + } + + thread_array_t thread_list; + mach_msg_type_number_t thread_count; + + thread_info_data_t thinfo; + mach_msg_type_number_t thread_info_count; + + thread_basic_info_t basic_info_th; + + // get threads in the task + kr = task_threads(mach_task_self(), &thread_list, &thread_count); + if (kr != KERN_SUCCESS) { + return nil; + } + + long tot_sec = 0; + long tot_usec = 0; + float tot_cpu = 0; + unsigned j; + + for (j = 0; j < thread_count; j++) { + thread_info_count = THREAD_INFO_MAX; + kr = thread_info(thread_list[j], THREAD_BASIC_INFO, + (thread_info_t)thinfo, &thread_info_count); + if (kr != KERN_SUCCESS) { + return nil; + } + + basic_info_th = (thread_basic_info_t)thinfo; + + if (!(basic_info_th->flags & TH_FLAGS_IDLE)) { + tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; + tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds; + tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0; + } + + } // for each thread + + kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); + + if( kr == KERN_SUCCESS ) { + return @(tot_cpu); + } else { + return nil; + + } } static NSDictionary *RCTProfileMergeArgs(NSDictionary *args0, NSDictionary *args1) @@ -369,7 +431,7 @@ void RCTProfileImmediateEvent( @"ts": RCTProfileTimestamp(CACurrentMediaTime()), @"scope": @(scope), @"ph": @"i", - @"args": RCTProfileGetMemoryUsage(), + @"args": RCTProfileGetMemoryUsage(NO), ); ); } diff --git a/packager/debugger.html b/packager/debugger.html index 24f8aea55..f152d9e17 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -14,8 +14,197 @@ React Native Debugger - + + +