mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-06 09:17:55 +08:00
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. <img width="951" alt="screen shot 2015-07-19 at 7 34 00 pm" src="https://cloud.githubusercontent.com/assets/1388079/8769257/edc42f70-2e4d-11e5-8813-e86ef530a446.png"> 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 <hharnisc@gmail.com>
250 lines
6.8 KiB
Objective-C
250 lines
6.8 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTDefines.h"
|
|
|
|
#if RCT_DEV // Debug executors are only supported in dev mode
|
|
|
|
#import "RCTWebSocketExecutor.h"
|
|
|
|
#import "RCTConvert.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTSparseArray.h"
|
|
#import "RCTUtils.h"
|
|
#import "RCTSRWebSocket.h"
|
|
#import "RCTProfile.h"
|
|
|
|
typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply);
|
|
|
|
@interface RCTWebSocketExecutor () <RCTSRWebSocketDelegate>
|
|
|
|
@end
|
|
|
|
@implementation RCTWebSocketExecutor
|
|
{
|
|
RCTSRWebSocket *_socket;
|
|
dispatch_queue_t _jsQueue;
|
|
RCTSparseArray *_callbacks;
|
|
dispatch_semaphore_t _socketOpenSemaphore;
|
|
NSMutableDictionary *_injectedObjects;
|
|
NSURL *_url;
|
|
}
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (instancetype)init
|
|
{
|
|
return [self initWithURL:[RCTConvert NSURL:@"http://localhost:8081/debugger-proxy"]];
|
|
}
|
|
|
|
- (instancetype)initWithURL:(NSURL *)URL
|
|
{
|
|
RCTAssertParam(URL);
|
|
|
|
if ((self = [super init])) {
|
|
_url = URL;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
_jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
|
|
_socket = [[RCTSRWebSocket alloc] initWithURL:_url];
|
|
_socket.delegate = self;
|
|
_callbacks = [RCTSparseArray new];
|
|
_injectedObjects = [NSMutableDictionary new];
|
|
[_socket setDelegateDispatchQueue:_jsQueue];
|
|
|
|
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:_url];
|
|
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil];
|
|
|
|
if (![self connectToProxy]) {
|
|
RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \
|
|
you are running on the device, check if you have the right IP \
|
|
address in `RCTWebSocketExecutor.m`.", _url);
|
|
[self invalidate];
|
|
return;
|
|
}
|
|
|
|
NSInteger retries = 3;
|
|
BOOL runtimeIsReady = [self prepareJSRuntime];
|
|
while (!runtimeIsReady && retries > 0) {
|
|
runtimeIsReady = [self prepareJSRuntime];
|
|
retries--;
|
|
}
|
|
if (!runtimeIsReady) {
|
|
RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not "
|
|
"paused on a breakpoint or exception and try reloading again.");
|
|
[self invalidate];
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (BOOL)connectToProxy
|
|
{
|
|
_socketOpenSemaphore = dispatch_semaphore_create(0);
|
|
[_socket open];
|
|
long connected = dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2));
|
|
return connected == 0;
|
|
}
|
|
|
|
- (BOOL)prepareJSRuntime
|
|
{
|
|
__block NSError *initError;
|
|
dispatch_semaphore_t s = dispatch_semaphore_create(0);
|
|
[self sendMessage:@{@"method": @"prepareJSRuntime"} waitForReply:^(NSError *error, NSDictionary *reply) {
|
|
initError = error;
|
|
dispatch_semaphore_signal(s);
|
|
}];
|
|
long runtimeIsReady = dispatch_semaphore_wait(s, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC));
|
|
return runtimeIsReady == 0 && initError == nil;
|
|
}
|
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
|
|
{
|
|
NSError *error = nil;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
|
|
{
|
|
dispatch_semaphore_signal(_socketOpenSemaphore);
|
|
}
|
|
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
|
{
|
|
RCTLogError(@"WebSocket connection failed with error %@", error);
|
|
}
|
|
|
|
- (void)sendMessage:(NSDictionary *)message waitForReply:(RCTWSMessageCallback)callback
|
|
{
|
|
static NSUInteger lastID = 10000;
|
|
|
|
dispatch_async(_jsQueue, ^{
|
|
if (!self.valid) {
|
|
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
|
|
NSLocalizedDescriptionKey: @"socket closed"
|
|
}];
|
|
callback(error, nil);
|
|
return;
|
|
}
|
|
|
|
NSNumber *expectedID = @(lastID++);
|
|
_callbacks[expectedID] = [callback copy];
|
|
NSMutableDictionary *messageWithID = [message mutableCopy];
|
|
messageWithID[@"id"] = expectedID;
|
|
[_socket send:RCTJSONStringify(messageWithID, NULL)];
|
|
});
|
|
}
|
|
|
|
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
|
{
|
|
NSDictionary *message = @{
|
|
@"method": @"executeApplicationScript",
|
|
@"url": RCTNullIfNil([URL absoluteString]),
|
|
@"inject": _injectedObjects,
|
|
};
|
|
[self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) {
|
|
onComplete(error);
|
|
}];
|
|
}
|
|
|
|
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
|
|
{
|
|
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
|
NSDictionary *message = @{
|
|
@"method": @"executeJSCall",
|
|
@"moduleName": name,
|
|
@"moduleMethod": method,
|
|
@"arguments": arguments
|
|
};
|
|
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {
|
|
if (socketError) {
|
|
onComplete(nil, socketError);
|
|
return;
|
|
}
|
|
|
|
NSString *result = reply[@"result"];
|
|
id objcValue = RCTJSONParse(result, NULL);
|
|
onComplete(objcValue, nil);
|
|
}];
|
|
}
|
|
|
|
- (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, ^{
|
|
_injectedObjects[objectName] = script;
|
|
onComplete(nil);
|
|
});
|
|
}
|
|
|
|
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
|
{
|
|
if ([NSThread isMainThread]) {
|
|
block();
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), block);
|
|
}
|
|
}
|
|
|
|
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), block);
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
_socket.delegate = nil;
|
|
[_socket closeWithCode:1000 reason:@"Invalidated"];
|
|
_socket = nil;
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
return _socket != nil && _socket.readyState == RCTSR_OPEN;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|