Skip to content

Commit

Permalink
Fixing view spans logic
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoEmbrace committed Dec 6, 2024
1 parent 485fe0b commit 6516f46
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 40 deletions.
51 changes: 29 additions & 22 deletions Sources/EmbraceCore/Capture/UX/View/UIViewControllerHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ class UIViewControllerHandler {
@ThreadSafe var alreadyFinishedUiReadyIds: Set<String> = []

init() {
NotificationCenter.default.addObserver(
Embrace.notificationCenter.addObserver(
self,
selector: #selector(appDidEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
selector: #selector(foregroundSessionDidEnd),
name: .embraceForegroundSessionDidEnd,
object: nil
)
}

deinit {
NotificationCenter.default.removeObserver(self)
Embrace.notificationCenter.removeObserver(self)
}

func parentSpan(for vc: UIViewController) -> Span? {
Expand All @@ -53,16 +53,18 @@ class UIViewControllerHandler {
return parentSpans[id]
}

@objc func appDidEnterBackground() {
@objc func foregroundSessionDidEnd(_ noticication: Notification? = nil) {
let now = noticication?.object as? Date ?? Date()

// end all parent spans and visibility spans if the app enters the background
// also clear all the cached spans
queue.async {
for span in self.visibilitySpans.values {
span.end()
span.end(time: now)
}

for id in self.parentSpans.keys {
self.forcefullyEndSpans(id: id)
self.forcefullyEndSpans(id: id, time: now)
}

self.parentSpans.removeAll()
Expand Down Expand Up @@ -206,8 +208,10 @@ class UIViewControllerHandler {
}

// end view did appear span
let now = Date()

if let span = self.viewDidAppearSpans.removeValue(forKey: id) {
span.end()
span.end(time: now)
}

guard let parentSpan = self.parentSpans[id] else {
Expand All @@ -216,7 +220,7 @@ class UIViewControllerHandler {

// end time to first render span
if parentSpan.isTimeToFirstRender {
parentSpan.end()
parentSpan.end(time: now)
self.clear(id: id, vc: vc)

// generate ui ready span
Expand All @@ -231,8 +235,8 @@ class UIViewControllerHandler {
// if the view controller was already flagged as ready to interact
// we end the spans right away
if self.alreadyFinishedUiReadyIds.contains(id) {
span.end()
parentSpan.end()
span.end(time: now)
parentSpan.end(time: now)

self.clear(id: id, vc: vc)

Expand All @@ -250,13 +254,16 @@ class UIViewControllerHandler {
return
}

let now = Date()

// end visibility span
if let span = self.visibilitySpans[id] {
span.end()
span.end(time: now)
self.visibilitySpans[id] = nil
}

// force end all spans
self.forcefullyEndSpans(id: id)
self.forcefullyEndSpans(id: id, time: now)
}
}

Expand All @@ -271,8 +278,9 @@ class UIViewControllerHandler {
// if we have a ui ready span it means that viewDidAppear already happened
// in this case we close the spans
if let span = self.uiReadySpans[id] {
span.end()
parentSpan.end()
let now = Date()
span.end(time: now)
parentSpan.end(time: now)
self.clear(id: id, vc: vc)

// otherwise it means the view is still loading, in this case we flag
Expand All @@ -284,26 +292,26 @@ class UIViewControllerHandler {
}
}

private func forcefullyEndSpans(id: String) {
private func forcefullyEndSpans(id: String, time: Date) {

if let viewDidLoadSpan = self.viewDidLoadSpans[id] {
viewDidLoadSpan.end(errorCode: .userAbandon)
viewDidLoadSpan.end(errorCode: .userAbandon, time: time)
}

if let viewWillAppearSpan = self.viewWillAppearSpans[id] {
viewWillAppearSpan.end(errorCode: .userAbandon)
viewWillAppearSpan.end(errorCode: .userAbandon, time: time)
}

if let viewDidAppearSpan = self.viewDidAppearSpans[id] {
viewDidAppearSpan.end(errorCode: .userAbandon)
viewDidAppearSpan.end(errorCode: .userAbandon, time: time)
}

if let uiReadySpan = self.uiReadySpans[id] {
uiReadySpan.end(errorCode: .userAbandon)
uiReadySpan.end(errorCode: .userAbandon, time: time)
}

if let parentSpan = self.parentSpans[id] {
parentSpan.end(errorCode: .userAbandon)
parentSpan.end(errorCode: .userAbandon, time: time)
}

self.clear(id: id)
Expand Down Expand Up @@ -338,7 +346,6 @@ class UIViewControllerHandler {
self.viewDidLoadSpans[id] = nil
self.viewWillAppearSpans[id] = nil
self.viewDidAppearSpans[id] = nil
self.visibilitySpans[id] = nil
self.uiReadySpans[id] = nil
self.alreadyFinishedUiReadyIds.remove(id)

Expand Down
18 changes: 13 additions & 5 deletions Sources/EmbraceCore/Session/SessionController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ class SessionController: SessionControllable {
let queue: DispatchQueue
var firstSession = true

internal var notificationCenter = NotificationCenter.default

init(
storage: EmbraceStorage,
upload: EmbraceUpload?,
Expand Down Expand Up @@ -133,7 +131,7 @@ class SessionController: SessionControllable {
heartbeat.start()

// post notification
notificationCenter.post(name: .embraceSessionDidStart, object: session)
NotificationCenter.default.post(name: .embraceSessionDidStart, object: session)

firstSession = false

Expand Down Expand Up @@ -165,15 +163,20 @@ class SessionController: SessionControllable {
// auto terminate spans
EmbraceOTel.processor?.autoTerminateSpans()

// post notification
notificationCenter.post(name: .embraceSessionWillEnd, object: currentSession)
// post public notification
NotificationCenter.default.post(name: .embraceSessionWillEnd, object: currentSession)

currentSessionSpan?.end(time: now)
SessionSpanUtils.setCleanExit(span: currentSessionSpan, cleanExit: true)

currentSession?.endTime = now
currentSession?.cleanExit = true

// post internal notification
if currentSession?.state == SessionState.foreground.rawValue {
Embrace.notificationCenter.post(name: .embraceForegroundSessionDidEnd, object: now)
}

// save session record
save()

Expand Down Expand Up @@ -242,3 +245,8 @@ extension SessionController {
currentSessionSpan = nil
}
}

// internal use
extension Notification.Name {
static let embraceForegroundSessionDidEnd = Notification.Name("embrace.session.foreground.end")
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class UIViewControllerHandlerTests: XCTestCase {
XCTAssertNil(parent)
}

func test_appDidEnterBackground_clearsCache() {
func test_foregroundSessionDidEnd_clearsCache() {
// given a handler with cached spans
let id = "test"
let span = createSpan()
Expand All @@ -66,16 +66,16 @@ class UIViewControllerHandlerTests: XCTestCase {
handler.uiReadySpans[id] = span
handler.alreadyFinishedUiReadyIds.insert(id)

// when appDidEnterBackground is called
handler.appDidEnterBackground()
// when foregroundSessionDidEnd is called
handler.foregroundSessionDidEnd()

// then the cache is cleared
wait {
return self.cacheIsEmpty()
return self.cacheIsEmpty(true)
}
}

func test_appDidEnterBackground_endsSpans() {
func test_foregroundSessionDidEnd_endsSpans() {
// given a handler with cached spans
let id = "test"

Expand All @@ -97,8 +97,8 @@ class UIViewControllerHandlerTests: XCTestCase {
let uiReadySpan = createUiReadySpan()
handler.uiReadySpans[id] = uiReadySpan

// when appDidEnterBackgroundz is called
handler.appDidEnterBackground()
// when appDidEnterBackground is called
handler.foregroundSessionDidEnd()

// then all spans are ended
wait {
Expand Down Expand Up @@ -206,7 +206,7 @@ class UIViewControllerHandlerTests: XCTestCase {
validateViewDidLoadSpans(vc: vc, parentName: parentName)
validateViewWillAppearSpans(vc: vc, parentName: parentName)

handler.appDidEnterBackground()
handler.foregroundSessionDidEnd()

wait(timeout: .longTimeout) {
let parent = self.otel.spanProcessor.endedSpans.first(where: { $0.name.contains(parentName) })
Expand Down Expand Up @@ -242,7 +242,7 @@ class UIViewControllerHandlerTests: XCTestCase {
let parent = self.otel.spanProcessor.endedSpans.first(where: { $0.name.contains(parentName) })
let uiReady = self.otel.spanProcessor.endedSpans.first(where: { $0.name == "ui-ready"})

return parent != nil && uiReady != nil && self.cacheIsEmpty()
return parent != nil && uiReady != nil && parent!.endTime == uiReady!.endTime && self.cacheIsEmpty()
}
}

Expand All @@ -264,7 +264,7 @@ class UIViewControllerHandlerTests: XCTestCase {
let parent = self.otel.spanProcessor.endedSpans.first(where: { $0.name.contains(parentName) })
let uiReady = self.otel.spanProcessor.endedSpans.first(where: { $0.name == "ui-ready"})

return parent != nil && uiReady != nil && self.cacheIsEmpty()
return parent != nil && uiReady != nil && parent!.endTime == uiReady!.endTime && self.cacheIsEmpty()
}
}

Expand Down Expand Up @@ -296,7 +296,7 @@ class UIViewControllerHandlerTests: XCTestCase {
validateViewDidLoadSpans(vc: vc, parentName: parentName)
validateViewWillAppearSpans(vc: vc, parentName: parentName)

handler.appDidEnterBackground()
handler.foregroundSessionDidEnd()

// then the spans are ended
wait(timeout: .longTimeout) {
Expand Down Expand Up @@ -375,12 +375,12 @@ class UIViewControllerHandlerTests: XCTestCase {
}
}

func cacheIsEmpty() -> Bool {
func cacheIsEmpty(_ checkVisibilitySpans: Bool = false) -> Bool {
return handler.parentSpans.count == 0 &&
handler.viewDidLoadSpans.count == 0 &&
handler.viewWillAppearSpans.count == 0 &&
handler.viewDidAppearSpans.count == 0 &&
handler.visibilitySpans.count == 0 &&
(!checkVisibilitySpans || handler.visibilitySpans.count == 0) &&
handler.uiReadySpans.count == 0 &&
handler.alreadyFinishedUiReadyIds.count == 0
}
Expand Down

0 comments on commit 6516f46

Please sign in to comment.