Skip to content

Commit

Permalink
Merge pull request #230 from wakatime/bugfix/performance
Browse files Browse the repository at this point in the history
Improve watcher performance by only starting timer for monitored apps
  • Loading branch information
alanhamlett authored Mar 19, 2024
2 parents 92c00a1 + d1e2dcf commit a6ec69b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 67 deletions.
6 changes: 3 additions & 3 deletions WakaTime/Extensions/AXUIElementExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ extension AXUIElement {
func elementAtIndexPath(_ indexPath: [Int]) -> AXUIElement? {
var currentElement: AXUIElement = self
for index in indexPath {
currentElement.debugPrint()
// currentElement.debugPrint()
guard let children = currentElement.children, index < children.count else {
// Index is out of bounds for the current element's children
return nil
Expand Down Expand Up @@ -413,7 +413,7 @@ extension AXUIElement {
let matches = regex.numberOfMatches(in: value, options: [], range: range)
return matches > 0
} catch {
print("Regex error: \(error.localizedDescription)")
// print("Regex error: \(error.localizedDescription)")
return false
}
}
Expand Down Expand Up @@ -512,7 +512,7 @@ extension AXUIElement {
}
}
} catch {
print("Regex error: \(error)")
Logging.default.log("Regex error: \(error)")
continue
}
}
Expand Down
22 changes: 12 additions & 10 deletions WakaTime/Helpers/EventSourceObserver.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
//
// EventSourceObserver.swift
// WakaTime
//
// Created by Tobias Lensing on 04.03.24.
//

import Foundation
import CoreGraphics

class EventSourceObserver {
let pollIntervalInSeconds: CFTimeInterval
var timer: Timer = Timer(timeInterval: 1, repeats: false) { _ in }

init(pollIntervalInSeconds: CFTimeInterval, activityDetected: @escaping () -> Void) {
init(pollIntervalInSeconds: CFTimeInterval) {
self.pollIntervalInSeconds = pollIntervalInSeconds
Timer.scheduledTimer(withTimeInterval: pollIntervalInSeconds, repeats: true) { _ in
timer.invalidate()
}

func start(activityDetected: @escaping () -> Void) {
stop()
timer = Timer.scheduledTimer(withTimeInterval: pollIntervalInSeconds, repeats: true) { [self] _ in
let secondsSinceLastKeyPress = Self.checkForKeyPresses()
let secondsSinceLastMouseMoved = Self.checkForMouseActivity()
if secondsSinceLastKeyPress < pollIntervalInSeconds || secondsSinceLastMouseMoved < pollIntervalInSeconds {
Expand All @@ -22,6 +20,10 @@ class EventSourceObserver {
}
}

func stop() {
timer.invalidate()
}

static private func checkForKeyPresses() -> CFTimeInterval {
CGEventSource.secondsSinceLastEventType(.combinedSessionState, eventType: .keyDown)
}
Expand Down
85 changes: 31 additions & 54 deletions WakaTime/Watcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,7 @@ class Watcher: NSObject {
override init() {
super.init()

eventSourceObserver = EventSourceObserver(pollIntervalInSeconds: 1) { [weak self] in
self?.callbackQueue.async {
guard
let app = self?.activeApp, !MonitoringManager.isAppXcode(app),
let bundleId = app.bundleIdentifier
else { return }

var heartbeat = MonitoringManager.heartbeatData(app)

if let heartbeat {
self?.lastValidHeartbeatForApp[bundleId] = heartbeat
} else {
heartbeat = self?.lastValidHeartbeatForApp[bundleId]
}

if let heartbeat {
self?.heartbeatEventHandler?.handleHeartbeatEvent(
app: app,
entity: heartbeat.entity,
entityType: EntityType.app,
project: heartbeat.project,
language: heartbeat.language,
category: heartbeat.category,
isWrite: false
)
}
}
}
eventSourceObserver = EventSourceObserver(pollIntervalInSeconds: 1)

NSWorkspace.shared.notificationCenter.addObserver(
self,
Expand All @@ -63,27 +36,6 @@ class Watcher: NSObject {
if let app = NSWorkspace.shared.frontmostApplication {
handleAppChanged(app)
}

/*
NSEvent.addGlobalMonitorForEvents(
matching: [NSEvent.EventTypeMask.keyDown],
handler: handleKeyboardEvent
)
*/

/*
do {
try EonilFSEvents.startWatching(
paths: ["/"],
for: ObjectIdentifier(self),
with: { event in
// Logging.default.log(event)
}
)
} catch {
Logging.default.log("Failed to setup FSEvents: \(error.localizedDescription)")
}
*/
}

deinit {
Expand All @@ -100,6 +52,7 @@ class Watcher: NSObject {
if app != activeApp {
// swiftlint:disable line_length
Logging.default.log("App changed from \(activeApp?.localizedName ?? "nil") to \(app.localizedName ?? "nil") (\(app.bundleIdentifier ?? "nil"))")
eventSourceObserver?.stop()
// swiftlint:enable line_length
if let oldApp = activeApp { unwatch(app: oldApp) }
activeApp = app
Expand All @@ -111,11 +64,6 @@ class Watcher: NSObject {
setAppVersion(app)
}

func handleKeyboardEvent(event: NSEvent!) {
// NSLog("keyDown")
// TODO: call eventHandler to send heartbeat
}

private func setAppVersion(_ app: NSRunningApplication) {
guard
let id = app.bundleIdentifier,
Expand Down Expand Up @@ -167,6 +115,35 @@ class Watcher: NSObject {
self.documentPath = currentPath
}
observeActivityText(activeWindow: activeWindow)
} else {
eventSourceObserver?.start { [weak self] in
self?.callbackQueue.async {
guard
let app = self?.activeApp, !MonitoringManager.isAppXcode(app),
let bundleId = app.bundleIdentifier
else { return }

var heartbeat = MonitoringManager.heartbeatData(app)

if let heartbeat {
self?.lastValidHeartbeatForApp[bundleId] = heartbeat
} else {
heartbeat = self?.lastValidHeartbeatForApp[bundleId]
}

if let heartbeat {
self?.heartbeatEventHandler?.handleHeartbeatEvent(
app: app,
entity: heartbeat.entity,
entityType: EntityType.app,
project: heartbeat.project,
language: heartbeat.language,
category: heartbeat.category,
isWrite: false
)
}
}
}
}
} catch {
Logging.default.log("Failed to setup AXObserver: \(error.localizedDescription)")
Expand Down

0 comments on commit a6ec69b

Please sign in to comment.