Skip to content

Commit

Permalink
Merge pull request #6 from diogobalseiro/master
Browse files Browse the repository at this point in the history
Fixes A Performance Bottleneck Related to Exporting
  • Loading branch information
diogobalseiro authored Oct 12, 2020
2 parents e42810f + b6bb85f commit 311b147
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 121 deletions.
2 changes: 1 addition & 1 deletion FNMNetworkMonitor.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |spec|
spec.name = 'FNMNetworkMonitor'
spec.module_name = 'FNMNetworkMonitor'
spec.version = '9.2.0'
spec.version = '10.0.0'
spec.summary = 'A network monitor'
spec.homepage = 'https://github.com/Farfetch/network-monitor-ios'
spec.license = 'MIT'
Expand Down
4 changes: 3 additions & 1 deletion NetworkMonitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1020;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = Farfetch;
TargetAttributes = {
F793CA4C1F9E361900AA9C41 = {
Expand Down Expand Up @@ -647,6 +647,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down Expand Up @@ -712,6 +713,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
Expand Down
78 changes: 55 additions & 23 deletions NetworkMonitor/Classes/Exporter/FNMRecordExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,73 @@

import Foundation

public enum FNMRecordExporterSortOption: String {
public enum FNMRecordExporterPreference {

case sortedStartTimestamp = "sorted-start-timestamp"
case sortedAlphabetically = "sorted-alphabetically"
case sortedSlowest = "sorted-slowest"
case off
case on(setting: FNMRecordExporterPreferenceSetting)

public enum FNMRecordExporterPreferenceSetting {

case unlimited
case first(numberOfRecords: Int)
case last(numberOfRecords: Int)
}
}

struct FNMRecordExporter {

static var requestRecordExportQueued: Bool = false

static func export(_ requestRecords: [FNMHTTPRequestRecord],
option: FNMRecordExporterSortOption) {
preference: FNMRecordExporterPreference) {

DispatchQueue.global().async {
if case let FNMRecordExporterPreference.on(setting) = preference {

do {
if self.requestRecordExportQueued == false {

let recordsFilenameURL = try self.recordsFilenameURL(option: option)
self.requestRecordExportQueued = true

let serializableObject = FNMHTTPRequestRecordCodableContainer(records: requestRecords,
option: option)
DispatchQueue.global().asyncAfter(deadline: .now() + Constants.exportDebounceDelay,
execute: {

try self.encodeObject(serializableObject,
fileUrl: recordsFilenameURL)
do {

FNMRecordExporter.log(message: "Exported Request Records Using Option '\(option)'")
let recordsFilenameURL = try self.recordsFilenameURL()
let requestRecordsToProcess: [FNMHTTPRequestRecord]

} catch {
switch setting {

assertionFailure("Failed to export, please advise")
case .unlimited:
requestRecordsToProcess = requestRecords

case .first(let numberOfRecords):
requestRecordsToProcess = Array(requestRecords.prefix(numberOfRecords))

case .last(let numberOfRecords):
requestRecordsToProcess = Array(requestRecords.suffix(numberOfRecords))
}

let serializableObject = FNMHTTPRequestRecordCodableContainer(records: requestRecordsToProcess)

try self.encodeObject(serializableObject,
fileUrl: recordsFilenameURL)

FNMRecordExporter.log(message: "Exported Request Records Using Setting '\(setting)'")

self.requestRecordExportQueued = false

} catch {

assertionFailure("Failed to export, please advise")
}
})
}
}
}

static func exportRecord(_ record: FNMRecord,
requestRecords: [FNMHTTPRequestRecord],
overallRecords: Bool = false) {
requestRecords: [FNMHTTPRequestRecord],
overallRecords: Bool = false) {

DispatchQueue.global().async {

Expand Down Expand Up @@ -86,14 +117,13 @@ extension FNMRecordExporter {
return self.currentRunConfigurationFilenameURL(path: currentRunFolderPath)
}

static func recordsFilenameURL(option: FNMRecordExporterSortOption) throws -> URL {
static func recordsFilenameURL() throws -> URL {

let folderPath = try self.relativeRecordsFolder(title: self.folderBaseName())

_ = try self.createFolder(at: folderPath)

return self.recordsFilenameURL(path: folderPath,
option: option)
return self.recordsFilenameURL(path: folderPath)
}

private static func log(message: String) {
Expand Down Expand Up @@ -152,14 +182,13 @@ extension FNMRecordExporter {
}
}

private static func recordsFilenameURL(path: URL,
option: FNMRecordExporterSortOption) -> URL {
private static func recordsFilenameURL(path: URL) -> URL {

var filePath = path

filePath.appendPathComponent(Constants.exportRecordsSimpleFilename +
Constants.exportFileSeparator +
option.rawValue +
Constants.exportRecordsFilename +
Constants.exportFileExtension)

return filePath
Expand Down Expand Up @@ -213,9 +242,12 @@ private extension FNMRecordExporter {
static let exportGeneralFolderPath = "AppLaunch"
static let exportCurrentRunFolderPath = "CurrentRun"
static let exportRecordsGeneralFolderPath = "Records"
static let exportRecordsFilename = "sorted-start-timestamp"
static let exportCurrentRunFilename = "configuration"
static let exportRecordsSimpleFilename = "records"
static let exportFileExtension = ".json"
static let exportFileSeparator = "-"

static let exportDebounceDelay = 1.5
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ struct FNMHTTPRequestRecordCodableContainer {

let records: [FNMHTTPRequestRecord]

init(records: [FNMHTTPRequestRecord],
option: FNMRecordExporterSortOption) {
init(records: [FNMHTTPRequestRecord]) {

self.records = type(of: self).sorted(records: records,
option: option)
self.records = type(of: self).sorted(records: records)
}
}

Expand All @@ -41,42 +39,11 @@ private extension FNMHTTPRequestRecordCodableContainer {

private extension FNMHTTPRequestRecordCodableContainer {

static func sorted(records: [FNMHTTPRequestRecord],
option: FNMRecordExporterSortOption) -> [FNMHTTPRequestRecord] {
static func sorted(records: [FNMHTTPRequestRecord]) -> [FNMHTTPRequestRecord] {

switch option {
case .sortedAlphabetically:
return records.sorted {

return records.sorted {

guard let recordAAbsoluteURL = $0.request.url?.absoluteString,
let recordAScheme = $0.request.url?.scheme,
let recordBAbsoluteURL = $1.request.url?.absoluteString,
let recordBScheme = $1.request.url?.scheme else { return false }

// The scheme is not relevant for this sort
let recordATrimmedURL = recordAAbsoluteURL.replacingOccurrences(of: recordAScheme, with: "")
let recordBTrimmedURL = recordBAbsoluteURL.replacingOccurrences(of: recordBScheme, with: "")

return recordATrimmedURL.compare(recordBTrimmedURL) == .orderedAscending
}

case .sortedSlowest:

return records.sorted {

guard let recordATimeSpent = $0.timeSpent,
let recordBTimeSpent = $1.timeSpent else { return false }

return recordATimeSpent > recordBTimeSpent
}

case .sortedStartTimestamp:

return records.sorted {

return $0.startTimestamp.timeIntervalSince1970 < $1.startTimestamp.timeIntervalSince1970
}
return $0.startTimestamp.timeIntervalSince1970 < $1.startTimestamp.timeIntervalSince1970
}
}

Expand Down
16 changes: 4 additions & 12 deletions NetworkMonitor/Classes/FNMNetworkMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public final class FNMNetworkMonitor: NSObject {
public var logScope: FNMLogScope?

/// Whether the passive export is enabled
public var passiveExport = false
public var passiveExportPreference: FNMRecordExporterPreference = .off

/// The current collection of records recorded so far
@objc
Expand All @@ -55,9 +55,7 @@ public final class FNMNetworkMonitor: NSObject {

/// Start monitoring the network
@objc
public func startMonitoring(passiveExport: Bool = false) {

self.passiveExport = passiveExport
public func startMonitoring() {

self.subscribe(observer: self)

Expand Down Expand Up @@ -176,13 +174,7 @@ public final class FNMNetworkMonitor: NSObject {
let localRecordsSequence = Array(localRecords.values)

FNMRecordExporter.export(localRecordsSequence,
option: .sortedAlphabetically)

FNMRecordExporter.export(localRecordsSequence,
option: .sortedStartTimestamp)

FNMRecordExporter.export(localRecordsSequence,
option: .sortedSlowest)
preference: self.passiveExportPreference)
}
}

Expand Down Expand Up @@ -324,7 +316,7 @@ extension FNMNetworkMonitor: FNMNetworkMonitorObserver {

public func recordsUpdated(records: [FNMHTTPRequestRecord]) {

guard self.passiveExport,
guard case FNMRecordExporterPreference.on(_) = self.passiveExportPreference,
records.filter({ $0.conclusion == nil }).count == 0 else { return }

self.exportRecordData()
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Preview:

```swift
FNMNetworkMonitor.registerToLoadingSystem()
FNMNetworkMonitor.shared.startMonitoring(passiveExport: false)
FNMNetworkMonitor.shared.startMonitoring()
```

2. Monitoring custom URLSessions by supplying the FNMMonitor URL Protocol:
Expand All @@ -37,7 +37,7 @@ FNMNetworkMonitor.shared.startMonitoring(passiveExport: false)
let sessionConfig = URLSessionConfiguration.ephemeral
sessionConfig.protocolClasses = FNMNetworkMonitor.normalizedURLProtocols()
self.customSession = URLSession(configuration: sessionConfig)
FNMNetworkMonitor.shared.startMonitoring(passiveExport: false)
FNMNetworkMonitor.shared.startMonitoring()
```

3. You can also take advantage of sizzling the URLSessionConfiguration creation to configure the URL Protocol to all sessions, allowing to monitor 3rd party SDKs too.
Expand All @@ -52,7 +52,7 @@ let profiles = [FNMProfile(request: request,
responseHolder: .keyValue(value: [ "FieldA": 1 ])
delay: 0.25)])]
FNMNetworkMonitor.shared.configure(profiles: profiles)
FNMNetworkMonitor.shared.startMonitoring(passiveExport: false)
FNMNetworkMonitor.shared.startMonitoring()
```

Make sure to follow steps 1, 2 or 3, depending on the URLSession that runs that particular request.
Expand All @@ -73,7 +73,7 @@ FNMNetworkMonitor.shared.logScope = [.export, .profile, .urlProtocol]
Finally, you can turn on the passive export and the requests will be exported to a json file inside a folder found the Documents application folder.

```swift
FNMNetworkMonitor.shared.startMonitoring(passiveExport: true)
FNMNetworkMonitor.shared.passiveExportPreference = FNMRecordExporterPreference.on(setting: .unlimited)
```

### Sample app
Expand Down
3 changes: 2 additions & 1 deletion Sample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ final class ViewController: UIViewController {
FNMNetworkMonitor.registerToLoadingSystem()
FNMNetworkMonitor.shared.configure(profiles: profiles)

FNMNetworkMonitor.shared.startMonitoring(passiveExport: true)
FNMNetworkMonitor.shared.startMonitoring()
FNMNetworkMonitor.shared.passiveExportPreference = .on(setting: .unlimited)
FNMNetworkMonitor.shared.logScope = [.export, .profile, .urlProtocol]
}

Expand Down
3 changes: 2 additions & 1 deletion Tests/NetworkMonitorAppLaunchTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class NetworkMonitorTests: NetworkMonitorUnitTests {
self.networkMonitor.configure(profiles: Constants.Sites.allCases.map { $0.profile })
self.networkMonitor.clear(completion: { } )
FNMNetworkMonitor.registerToLoadingSystem()
FNMNetworkMonitor.shared.startMonitoring(passiveExport: true)
FNMNetworkMonitor.shared.startMonitoring()
FNMNetworkMonitor.shared.passiveExportPreference = .on(setting: .unlimited)

let debugListingViewController = FNMDebugListingViewController()
debugListingViewController.view.layoutIfNeeded()
Expand Down
4 changes: 2 additions & 2 deletions Tests/NetworkMonitorDebugUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class NetworkMonitorDebugUITests: NetworkMonitorUnitTests {
XCTAssertEqual(self.networkMonitor.records.count, 0)

FNMNetworkMonitor.registerToLoadingSystem()
FNMNetworkMonitor.shared.startMonitoring(passiveExport: false)
FNMNetworkMonitor.shared.startMonitoring()

let debugListingViewController = FNMDebugListingViewController()
debugListingViewController.view.layoutIfNeeded()
Expand All @@ -43,7 +43,7 @@ class NetworkMonitorDebugUITests: NetworkMonitorUnitTests {
self.networkMonitor.configure(profiles: Constants.Sites.allCases.map { $0.profile })
self.networkMonitor.clear(completion: { } )
FNMNetworkMonitor.registerToLoadingSystem()
FNMNetworkMonitor.shared.startMonitoring(passiveExport: false)
FNMNetworkMonitor.shared.startMonitoring()

let robotsExpectation = expectation(description: "Some Robots")
self.reachSitesSequencially(sites: [.alphabet], expectation: robotsExpectation)
Expand Down
Loading

0 comments on commit 311b147

Please sign in to comment.