Skip to content

Commit

Permalink
Merge pull request #94 from pingchen114/runtime-improve
Browse files Browse the repository at this point in the history
Runtime improvement
  • Loading branch information
RobertGummesson authored Jun 26, 2019
2 parents 101e567 + 016fd17 commit 160d20d
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 78 deletions.
4 changes: 3 additions & 1 deletion BuildTimeAnalyzer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "Cane Media Ltd";
TargetAttributes = {
2AF8213F1D21D6B900D65186 = {
Expand Down Expand Up @@ -353,6 +353,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand Down Expand Up @@ -408,6 +409,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
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 = "0930"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down Expand Up @@ -59,6 +59,7 @@
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
stopOnEveryMainThreadCheckerIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
Expand Down
20 changes: 11 additions & 9 deletions BuildTimeAnalyzer/CompileMeasure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ import Foundation
}

init?(time: Double, rawPath: String, code: String, references: Int) {
let untrimmedFilename = rawPath.split(separator: "/").map(String.init).last

guard let filepath = rawPath.split(separator: ":").map(String.init).first,
let filename = untrimmedFilename?.split(separator: ":").map(String.init).first else { return nil }

let locationString = String(rawPath[filepath.endIndex...].dropFirst())
let locations = locationString.split(separator: ":").compactMap{ Int(String.init($0)) }
let untrimmedFilename: Substring
if let lastIdx = rawPath.lastIndex(of: "/") {
untrimmedFilename = rawPath.suffix(from: rawPath.index(after: lastIdx))
} else {
untrimmedFilename = rawPath[...]
}
let filepath = rawPath.prefix(while: {$0 != ":"})
let filename = untrimmedFilename.prefix(while: {$0 != ":"})
let locations = untrimmedFilename.split(separator: ":").dropFirst().compactMap({Int(String($0))})
guard locations.count == 2 else { return nil }

self.time = time
self.code = code
self.path = filepath
self.filename = filename
self.path = String(filepath)
self.filename = String(filename)
self.locationArray = locations
self.references = references
}
Expand Down
135 changes: 70 additions & 65 deletions BuildTimeAnalyzer/LogProcessor.swift
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Foundation

typealias CMUpdateClosure = (_ result: [CompileMeasure], _ didComplete: Bool, _ didCancel: Bool) -> ()

fileprivate let regex = try! NSRegularExpression(pattern: "^\\d*\\.?\\d*ms\\t/", options: [])

protocol LogProcessorProtocol: class {
var rawMeasures: [String: RawMeasure] { get set }
var updateHandler: CMUpdateClosure? { get set }
Expand All @@ -16,86 +18,117 @@ protocol LogProcessorProtocol: class {
func processingDidFinish()
}

extension LogProcessorProtocol {
class LogProcessor: NSObject, LogProcessorProtocol {

var rawMeasures: [String: RawMeasure] = [:]
var updateHandler: CMUpdateClosure?
var shouldCancel = false
var timer: Timer?

func processingDidStart() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(self.timerCallback(_:)), userInfo: nil, repeats: true)
}
}

func processingDidFinish() {
DispatchQueue.main.async {
self.timer?.invalidate()
self.timer = nil
let didCancel = self.shouldCancel
self.shouldCancel = false
self.updateResults(didComplete: true, didCancel: didCancel)
}
}

@objc func timerCallback(_ timer: Timer) {
updateResults(didComplete: false, didCancel: false)
}

func processDatabase(database: XcodeDatabase, updateHandler: CMUpdateClosure?) {
guard let text = database.processLog() else {
updateHandler?([], true, false)
return
}

self.updateHandler = updateHandler
DispatchQueue.global().async {
DispatchQueue.global(qos: .background).async {
self.process(text: text)
}
}

// MARK: Private methods

private func process(text: String) {
let characterSet = CharacterSet(charactersIn:"\r\"")
let characterSet = CharacterSet(charactersIn:"\r")
var remainingRange = text.startIndex..<text.endIndex
let regex = try! NSRegularExpression(pattern: "^\\d*\\.?\\d*ms\\t/", options: [])


rawMeasures.removeAll()

processingDidStart()

while let nextRange = text.rangeOfCharacter(from: characterSet, options: [], range: remainingRange) {
let text = String(text[remainingRange.lowerBound..<nextRange.upperBound])


while !shouldCancel, let characterRange = text.rangeOfCharacter(from: characterSet,
options: .literal,
range: remainingRange) {
let nextRange = remainingRange.lowerBound..<characterRange.upperBound

defer {
remainingRange = nextRange.upperBound..<remainingRange.upperBound
}

// From LuizZak: (text as NSString).length improves the performance by about 2x compared to text.characters.count
let range = NSMakeRange(0, (text as NSString).length)

let range = NSRange(nextRange, in: text)
guard let match = regex.firstMatch(in: text, options: [], range: range) else { continue }

let timeString = text[..<text.index(text.startIndex, offsetBy: match.range.length - 4)]
let matchRange = Range<String.Index>.init(match.range, in: text)!
let timeString = text[remainingRange.lowerBound..<text.index(matchRange.upperBound, offsetBy: -4)]
if let time = Double(timeString) {
let value = String(text[text.index(text.startIndex, offsetBy: match.range.length - 1)...])
if var rawMeasure = rawMeasures[value] {
let value = String(text[text.index(before: matchRange.upperBound)..<nextRange.upperBound])
if let rawMeasure = rawMeasures[value] {
rawMeasure.time += time
rawMeasure.references += 1
rawMeasures[value] = rawMeasure
} else {
rawMeasures[value] = RawMeasure(time: time, text: value)
}
}
guard !shouldCancel else { break }
}
processingDidFinish()
}

fileprivate func updateResults(didComplete completed: Bool, didCancel: Bool) {
var filteredResults = rawMeasures.values.filter{ $0.time > 10 }
if filteredResults.count < 20 {
filteredResults = rawMeasures.values.filter{ $0.time > 0.1 }
}

let sortedResults = filteredResults.sorted(by: { $0.time > $1.time })
updateHandler?(processResult(sortedResults), completed, didCancel)

if completed {
rawMeasures.removeAll()
DispatchQueue.global(qos: .userInteractive).async {
let measures = self.rawMeasures.values
var filteredResults = measures.filter{ $0.time > 10 }
if filteredResults.count < 20 {
filteredResults = measures.filter{ $0.time > 0.1 }
}

let sortedResults = filteredResults.sorted(by: { $0.time > $1.time })
let result = self.processResult(sortedResults)

if completed {
self.rawMeasures.removeAll()
}

DispatchQueue.main.async {
self.updateHandler?(result, completed, didCancel)
}
}
}

private func processResult(_ unprocessedResult: [RawMeasure]) -> [CompileMeasure] {
let characterSet = CharacterSet(charactersIn:"\r\"")

var result: [CompileMeasure] = []
for entry in unprocessedResult {
let code = entry.text.split(separator: "\t").map(String.init)
let method = code.count >= 2 ? trimPrefixes(code[1]) : "-"

if let path = code.first?.trimmingCharacters(in: characterSet), let measure = CompileMeasure(time: entry.time, rawPath: path, code: method, references: entry.references) {
result.append(measure)
}
}
return result
}

private func trimPrefixes(_ code: String) -> String {
var code = code
["@objc ", "final ", "@IBAction "].forEach { (prefix) in
Expand All @@ -106,31 +139,3 @@ extension LogProcessorProtocol {
return code
}
}

class LogProcessor: NSObject, LogProcessorProtocol {

var rawMeasures: [String: RawMeasure] = [:]
var updateHandler: CMUpdateClosure?
var shouldCancel = false
var timer: Timer?

func processingDidStart() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(self.timerCallback(_:)), userInfo: nil, repeats: true)
}
}

func processingDidFinish() {
DispatchQueue.main.async {
self.timer?.invalidate()
self.timer = nil
let didCancel = self.shouldCancel
self.shouldCancel = false
self.updateResults(didComplete: true, didCancel: didCancel)
}
}

@objc func timerCallback(_ timer: Timer) {
updateResults(didComplete: false, didCancel: false)
}
}
Empty file modified BuildTimeAnalyzer/ProjectSelection.swift
100644 → 100755
Empty file.
6 changes: 4 additions & 2 deletions BuildTimeAnalyzer/RawMeasure.swift
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import Foundation

struct RawMeasure {
class RawMeasure {
var time: Double
var text: String
var references: Int
Expand All @@ -28,7 +28,9 @@ func ==(lhs: RawMeasure, rhs: RawMeasure) -> Bool {
// MARK: Hashable

extension RawMeasure: Hashable {

func hash(into hasher: inout Hasher) {
hasher.combine(time.hashValue ^ text.hashValue)
hasher.combine(time)
hasher.combine(text)
}
}
Empty file modified BuildTimeAnalyzer/ViewController.swift
100644 → 100755
Empty file.

0 comments on commit 160d20d

Please sign in to comment.