Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
// Copyright 2004-present Facebook. All Rights Reserved.
//
// FLEXNetworkObserver.h
// Derived from:
//
// PDAFNetworkDomainController.h
// PonyDebugger
//
// Created by Mike Lewis on 2/27/12.
//
// Licensed to Square, Inc. under one or more contributor license agreements.
// See the LICENSE file distributed with this work for the terms under
// which Square, Inc. licenses this file to you.
//
#import <Foundation/Foundation.h>
FOUNDATION_EXTERN NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system.
/// High level network events are sent to the default FLEXNetworkRecorder instance which maintains the request history and caches response bodies.
@interface FLEXNetworkObserver : NSObject
+ (void)start;
@end

View File

@@ -0,0 +1,70 @@
//
// FLEXNetworkRecorder.h
// Flipboard
//
// Created by Ryan Olson on 2/4/15.
// Copyright 2004-present Facebook. All Rights Reserved.
//
#import <Foundation/Foundation.h>
#import <SonarKitNetworkPlugin/SKNetworkReporter.h>
// Notifications posted when the record is updated
extern NSString *const kFLEXNetworkRecorderNewTransactionNotification;
extern NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification;
extern NSString *const kFLEXNetworkRecorderUserInfoTransactionKey;
extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
@class FLEXNetworkTransaction;
@interface FLEXNetworkRecorder : NSObject
/// In general, it only makes sense to have one recorder for the entire application.
+ (instancetype)defaultRecorder;
@property (nonatomic, weak) id<SKNetworkReporterDelegate> delegate;
/// Defaults to 25 MB if never set. Values set here are presisted across launches of the app.
@property (nonatomic, assign) NSUInteger responseCacheByteLimit;
/// If NO, the recorder not cache will not cache response for content types with an "image", "video", or "audio" prefix.
@property (nonatomic, assign) BOOL shouldCacheMediaResponses;
@property (nonatomic, copy) NSArray<NSString *> *hostBlacklist;
// Accessing recorded network activity
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
/// Dumps all network transactions and cached response bodies.
- (void)clearRecordedActivity;
// Recording network activity
/// Call when app is about to send HTTP request.
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
/// Call when HTTP response is available.
- (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response;
/// Call when data chunk is received over the network.
- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength;
/// Call when HTTP request has finished loading.
- (void)recordLoadingFinishedWithRequestID:(NSString *)requestID responseBody:(NSData *)responseBody;
/// Call when HTTP request has failed to load.
- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error;
/// Call to set the request mechanism anytime after recordRequestWillBeSent... has been called.
/// This string can be set to anything useful about the API used to make the request.
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID;
@end

View File

@@ -0,0 +1,235 @@
//
// FLEXNetworkRecorder.m
// Flipboard
//
// Created by Ryan Olson on 2/4/15.
// Copyright 2004-present Facebook. All Rights Reserved.
//
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXUtility.h"
NSString *const kFLEXNetworkRecorderNewTransactionNotification = @"kFLEXNetworkRecorderNewTransactionNotification";
NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification = @"kFLEXNetworkRecorderTransactionUpdatedNotification";
NSString *const kFLEXNetworkRecorderUserInfoTransactionKey = @"transaction";
NSString *const kFLEXNetworkRecorderTransactionsClearedNotification = @"kFLEXNetworkRecorderTransactionsClearedNotification";
NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.responseCacheLimit";
@interface FLEXNetworkRecorder ()
@property (nonatomic, strong) NSCache *responseCache;
@property (nonatomic, strong) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *networkTransactionsForRequestIdentifiers;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *identifierDict;
@end
@implementation FLEXNetworkRecorder
- (instancetype)init
{
self = [super init];
if (self) {
_responseCache = [NSCache new];
NSUInteger responseCacheLimit = [[[NSUserDefaults standardUserDefaults] objectForKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey] unsignedIntegerValue];
if (responseCacheLimit) {
[_responseCache setTotalCostLimit:responseCacheLimit];
} else {
// Default to 25 MB max. The cache will purge earlier if there is memory pressure.
[_responseCache setTotalCostLimit:25 * 1024 * 1024];
}
_orderedTransactions = [NSMutableArray array];
_networkTransactionsForRequestIdentifiers = [NSMutableDictionary dictionary];
// Serial queue used because we use mutable objects that are not thread safe
_queue = dispatch_queue_create("com.flex.FLEXNetworkRecorder", DISPATCH_QUEUE_SERIAL);
_identifierDict = [NSMutableDictionary dictionary];
}
return self;
}
+ (instancetype)defaultRecorder
{
static FLEXNetworkRecorder *defaultRecorder = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultRecorder = [[[self class] alloc] init];
});
return defaultRecorder;
}
#pragma mark - Public Data Access
- (void)setDelegate:(id<SKNetworkReporterDelegate>)delegate {
_delegate = delegate;
}
- (NSUInteger)responseCacheByteLimit
{
return [self.responseCache totalCostLimit];
}
- (void)setResponseCacheByteLimit:(NSUInteger)responseCacheByteLimit
{
[self.responseCache setTotalCostLimit:responseCacheByteLimit];
[[NSUserDefaults standardUserDefaults] setObject:@(responseCacheByteLimit) forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey];
}
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions
{
__block NSArray<FLEXNetworkTransaction *> *transactions = nil;
dispatch_sync(self.queue, ^{
transactions = [self.orderedTransactions copy];
});
return transactions;
}
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction
{
return [self.responseCache objectForKey:transaction.requestID];
}
- (void)clearRecordedActivity
{
dispatch_async(self.queue, ^{
[self.responseCache removeAllObjects];
[self.orderedTransactions removeAllObjects];
[self.networkTransactionsForRequestIdentifiers removeAllObjects];
});
}
#pragma mark - Network Events
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
if (![self.identifierDict objectForKey:requestID]) {
self.identifierDict[requestID] = [NSNumber random];
}
NSDate *startDate = [NSDate date];
if (redirectResponse) {
[self recordResponseReceivedWithRequestID:requestID response:redirectResponse];
[self recordLoadingFinishedWithRequestID:requestID responseBody:nil];
}
dispatch_async(self.queue, ^{
RequestInfo info = {
.identifier = self.identifierDict[requestID].longLongValue,
.timestamp = [NSDate timestamp],
.request = request,
};
info.setBody(request.HTTPBody);
[self.delegate didObserveRequest:info];
FLEXNetworkTransaction *transaction = [FLEXNetworkTransaction new];
transaction.requestID = requestID;
transaction.request = request;
transaction.startTime = startDate;
[self.orderedTransactions insertObject:transaction atIndex:0];
[self.networkTransactionsForRequestIdentifiers setObject:transaction forKey:requestID];
transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse;
});
}
/// Call when HTTP response is available.
- (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response
{
NSDate *responseDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
if (!transaction) {
return;
}
transaction.response = response;
transaction.transactionState = FLEXNetworkTransactionStateReceivingData;
transaction.latency = -[transaction.startTime timeIntervalSinceDate:responseDate];
});
}
/// Call when data chunk is received over the network.
- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength
{
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
if (!transaction) {
return;
}
transaction.receivedDataLength += dataLength;
});
}
/// Call when HTTP request has finished loading.
- (void)recordLoadingFinishedWithRequestID:(NSString *)requestID responseBody:(NSData *)responseBody
{
NSDate *finishedDate = [NSDate date];
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
if (!transaction) {
return;
}
transaction.transactionState = FLEXNetworkTransactionStateFinished;
transaction.duration = -[transaction.startTime timeIntervalSinceDate:finishedDate];
ResponseInfo responseInfo = {
.identifier = self.identifierDict[requestID].longLongValue,
.timestamp = [NSDate timestamp],
.response = transaction.response,
.body = nil,
};
responseInfo.setBody(responseBody);
self.identifierDict[requestID] = nil; //Clear the entry
[self.delegate didObserveResponse:responseInfo];
BOOL shouldCache = [responseBody length] > 0;
if (!self.shouldCacheMediaResponses) {
NSArray<NSString *> *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
for (NSString *ignoredPrefix in ignoredMIMETypePrefixes) {
shouldCache = shouldCache && ![transaction.response.MIMEType hasPrefix:ignoredPrefix];
}
}
if (shouldCache) {
[self.responseCache setObject:responseBody forKey:requestID cost:[responseBody length]];
}
});
}
- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error
{
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
if (!transaction) {
return;
}
ResponseInfo responseInfo = {
.identifier = self.identifierDict[requestID].longLongValue,
.timestamp = [NSDate timestamp],
.response = transaction.response,
.body = nil,
};
self.identifierDict[requestID] = nil; //Clear the entry
[self.delegate didObserveResponse:responseInfo];
transaction.transactionState = FLEXNetworkTransactionStateFailed;
transaction.duration = -[transaction.startTime timeIntervalSinceNow];
transaction.error = error;
});
}
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID
{
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
if (!transaction) {
return;
}
transaction.requestMechanism = mechanism;
});
}
@end

View File

@@ -0,0 +1,45 @@
//
// FLEXNetworkTransaction.h
// Flipboard
//
// Created by Ryan Olson on 2/8/15.
// Copyright 2004-present Facebook. All Rights Reserved.
//
#import <Foundation/Foundation.h>
#import "UIKit/UIKit.h"
typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
FLEXNetworkTransactionStateUnstarted,
FLEXNetworkTransactionStateAwaitingResponse,
FLEXNetworkTransactionStateReceivingData,
FLEXNetworkTransactionStateFinished,
FLEXNetworkTransactionStateFailed
};
@interface FLEXNetworkTransaction : NSObject
@property (nonatomic, copy) NSString *requestID;
@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, strong) NSURLResponse *response;
@property (nonatomic, copy) NSString *requestMechanism;
@property (nonatomic, assign) FLEXNetworkTransactionState transactionState;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, strong) NSDate *startTime;
@property (nonatomic, assign) NSTimeInterval latency;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) int64_t receivedDataLength;
/// Only applicable for image downloads. A small thumbnail to preview the full response.
@property (nonatomic, strong) UIImage *responseThumbnail;
/// Populated lazily. Handles both normal HTTPBody data and HTTPBodyStreams.
@property (nonatomic, strong, readonly) NSData *cachedRequestBody;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
@end

View File

@@ -0,0 +1,74 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "FLEXNetworkTransaction.h"
@interface FLEXNetworkTransaction ()
@property (nonatomic, strong, readwrite) NSData *cachedRequestBody;
@end
@implementation FLEXNetworkTransaction
- (NSString *)description
{
NSString *description = [super description];
description = [description stringByAppendingFormat:@" id = %@;", self.requestID];
description = [description stringByAppendingFormat:@" url = %@;", self.request.URL];
description = [description stringByAppendingFormat:@" duration = %f;", self.duration];
description = [description stringByAppendingFormat:@" receivedDataLength = %lld", self.receivedDataLength];
return description;
}
- (NSData *)cachedRequestBody {
if (!_cachedRequestBody) {
if (self.request.HTTPBody != nil) {
_cachedRequestBody = self.request.HTTPBody;
} else if ([self.request.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
NSInputStream *bodyStream = [self.request.HTTPBodyStream copy];
const NSUInteger bufferSize = 1024;
uint8_t buffer[bufferSize];
NSMutableData *data = [NSMutableData data];
[bodyStream open];
NSInteger readBytes = 0;
do {
readBytes = [bodyStream read:buffer maxLength:bufferSize];
[data appendBytes:buffer length:readBytes];
} while (readBytes > 0);
[bodyStream close];
_cachedRequestBody = data;
}
}
return _cachedRequestBody;
}
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state
{
NSString *readableString = nil;
switch (state) {
case FLEXNetworkTransactionStateUnstarted:
readableString = @"Unstarted";
break;
case FLEXNetworkTransactionStateAwaitingResponse:
readableString = @"Awaiting Response";
break;
case FLEXNetworkTransactionStateReceivingData:
readableString = @"Receiving Data";
break;
case FLEXNetworkTransactionStateFinished:
readableString = @"Finished";
break;
case FLEXNetworkTransactionStateFailed:
readableString = @"Failed";
break;
}
return readableString;
}
@end

View File

@@ -0,0 +1,34 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Availability.h>
#import <AvailabilityInternal.h>
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#define FLEXFloor(x) (floor([[UIScreen mainScreen] scale] * (x)) / [[UIScreen mainScreen] scale])
#define FLEX_AT_LEAST_IOS11_SDK defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
@interface NSNumber (SonarUtility)
+ (NSNumber *)random;
@end
@interface NSDate (SonarUtility)
+ (uint64_t)timestamp;
@end
@interface FLEXUtility : NSObject
// Swizzling utilities
+ (SEL)swizzledSelectorForSelector:(SEL)selector;
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls;
+ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)className withBlock:(id)block swizzledSelector:(SEL)swizzledSelector;
+ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock;
@end

View File

@@ -0,0 +1,117 @@
//
// FLEXUtility.m
// Flipboard
//
// Created by Ryan Olson on 4/18/14.
// Copyright 2004-present Facebook. All Rights Reserved.
//
#import "FLEXUtility.h"
#import <objc/runtime.h>
#include <assert.h>
#import <zlib.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#import <ImageIO/ImageIO.h>
@implementation FLEXUtility
+ (SEL)swizzledSelectorForSelector:(SEL)selector
{
return NSSelectorFromString([NSString stringWithFormat:@"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)]);
}
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls
{
if ([cls instancesRespondToSelector:selector]) {
unsigned int numMethods = 0;
Method *methods = class_copyMethodList(cls, &numMethods);
BOOL implementsSelector = NO;
for (int index = 0; index < numMethods; index++) {
SEL methodSelector = method_getName(methods[index]);
if (selector == methodSelector) {
implementsSelector = YES;
break;
}
}
free(methods);
if (!implementsSelector) {
return YES;
}
}
return NO;
}
+ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)className withBlock:(id)block swizzledSelector:(SEL)swizzledSelector
{
// This method is only intended for swizzling methods that are know to exist on the class.
// Bail if that isn't the case.
Method originalMethod = class_getInstanceMethod(className, originalSelector);
if (!originalMethod) {
return;
}
IMP implementation = imp_implementationWithBlock(block);
class_addMethod(className, swizzledSelector, implementation, method_getTypeEncoding(originalMethod));
Method newMethod = class_getInstanceMethod(className, swizzledSelector);
method_exchangeImplementations(originalMethod, newMethod);
}
+ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock
{
if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
return;
}
IMP implementation = imp_implementationWithBlock((id)([cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock));
Method oldMethod = class_getInstanceMethod(cls, selector);
if (oldMethod) {
class_addMethod(cls, swizzledSelector, implementation, methodDescription.types);
Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(oldMethod, newMethod);
} else {
class_addMethod(cls, selector, implementation, methodDescription.types);
}
}
@end
@implementation NSNumber (SonarUtility)
+ (NSNumber *)random {
int64_t identifier;
arc4random_buf(&identifier, sizeof(int64_t));
return @(identifier);
}
@end
@implementation NSDate (SonarUtility)
+ (uint64_t)getTimeNanoseconds
{
static struct mach_timebase_info tb_info = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__unused int ret = mach_timebase_info(&tb_info);
assert(0 == ret);
});
return (mach_absolute_time() * tb_info.numer) / tb_info.denom;
}
+ (uint64_t)timestamp {
const uint64_t nowNanoSeconds = [self getTimeNanoseconds];
return nowNanoSeconds / 1000000;
}
@end

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#if FB_SONARKIT_ENABLED
#import <Foundation/Foundation.h>
#import <SonarKitNetworkPlugin/SKNetworkReporter.h>
@interface SKIOSNetworkAdapter : NSObject<SKNetworkAdapterDelegate>
- (instancetype)init NS_DESIGNATED_INITIALIZER;
@property (weak, nonatomic) id<SKNetworkReporterDelegate> delegate;
@end
#endif

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#if FB_SONARKIT_ENABLED
#import "SKIOSNetworkAdapter.h"
#import "FLEXNetworkObserver.h"
#import "FLEXNetworkRecorder.h"
@implementation SKIOSNetworkAdapter
@synthesize delegate = _delegate;
- (instancetype)init{
if (self=[super init]){
_delegate = nil;
}
return self;
}
- (void)setDelegate:(id<SKNetworkReporterDelegate>)delegate {
_delegate = delegate;
[FLEXNetworkObserver start];
[FLEXNetworkRecorder defaultRecorder].delegate = _delegate;
}
@end
#endif