-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RUMM-1064 AppStateListener added #436
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get the idea, looks good 👍. Left important questions on threading and listener's life-cycle.
Sources/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListener.swift
Outdated
Show resolved
Hide resolved
Sources/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListener.swift
Outdated
Show resolved
Hide resolved
Sources/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListener.swift
Outdated
Show resolved
Hide resolved
Sources/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListener.swift
Outdated
Show resolved
Hide resolved
Sources/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListener.swift
Outdated
Show resolved
Hide resolved
Sources/Datadog/URLSessionAutoInstrumentation/Interception/TaskInterception.swift
Outdated
Show resolved
Hide resolved
.../DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift
Outdated
Show resolved
Hide resolved
.../DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift
Show resolved
Hide resolved
9276c8d
to
a5496e5
Compare
ee69ce5
to
8c716a5
Compare
8c716a5
to
39d3854
Compare
span.setTag(key: "foreground_duration", value: "\(appStateHistory.foregroundDuration)") | ||
span.setTag(key: "is_background", value: "\(appStateHistory.didRunInBackground)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- should they be declared in
DDTags
? (unlike otherDDTags
these are not officially supported by Datadog) - should they be added as
tag
s? (i couldn't think of any other place for these)
cc @ncreated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use internal
const defined in DDTag
for those (as we do for few other tags).
In APM tags are encoded as meta.*
attributes and those are always send as String
. This would make querying or aggregating impossible. In contrast to meta.*
, APM defines also metrics.*
which are numerics.
IMO sending "foreground_duration"
as String
makes little sense - the user won't be able to do anything with it in APM analytics. We should rather remove that one and keep only "is_background"
or send "foreground_duration"
as a custom metric (which would require new implementation in DDSpan
and SpanEncoder
as we don't support metrics yet).
WDYT @xgouchet @mariusc83 @0xnm ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i removed string conversion, now it's
span.setTag(key: "foreground_duration", value: appStateHistory.foregroundDuration.toNanoseconds)
with this, you can create a measure
(open the trace, click on foreground_duration
tag, select add facet, select measure
tab at the top) and filter traces longer/shorter than your threshold
NOTE: once you do that, it takes some time (a few minutes maybe) before you can use it in your query. at first i thought it wasn't working but it actually works 👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AppStateHistory
looks good 👍, but the threading model proposed for AppStateListener
is incorrect.
W need to solve a classic readers-writers problem with:
- occasional writes (when the app transitions between background and foreground)
- intensive reads (when a network request completes).
The model implemented in this PR doesn't leverage those conditions and it treats writes and reads equally. Because of writes being always performed from the main thread, the main thread receives a penalty from reads happening on background thread.
Given above conditions, the optimal threading model should work this way:
- occasional writes should be not blocking (no locks executed on the main thread),
- intensive reads should be concurrent (allowing simultaneous reads).
This can be achieved with a concurrent DispatchQueue
(simultaneous reads) and .barrier
API (for exclusive writes).
This problem was solved in #422 where we introduced ValuePublisher
type. We can leverage it this way:
let appStateHistory = ValuePublisher<AppStateHistory>()
// From the main thread, non-blocking async write:
appStateHistory.publishAsync(newAppStateHistory)
// From any background thread, concurrent read:
_ = appStateHistory.currentValue
span.setTag(key: "foreground_duration", value: "\(appStateHistory.foregroundDuration)") | ||
span.setTag(key: "is_background", value: "\(appStateHistory.didRunInBackground)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use internal
const defined in DDTag
for those (as we do for few other tags).
In APM tags are encoded as meta.*
attributes and those are always send as String
. This would make querying or aggregating impossible. In contrast to meta.*
, APM defines also metrics.*
which are numerics.
IMO sending "foreground_duration"
as String
makes little sense - the user won't be able to do anything with it in APM analytics. We should rather remove that one and keep only "is_background"
or send "foreground_duration"
as a custom metric (which would require new implementation in DDSpan
and SpanEncoder
as we don't support metrics yet).
WDYT @xgouchet @mariusc83 @0xnm ?
Sources/Datadog/URLSessionAutoInstrumentation/Interception/TaskInterception.swift
Outdated
Show resolved
Hide resolved
.../DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift
Show resolved
Hide resolved
3808aca
to
866d2a3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👌, except few comments to clean up.
let appStateHistory = appStateListener.history.take( | ||
between: resourceMetrics.fetch.start...resourceMetrics.fetch.end | ||
) | ||
// TODO: RUMM-1064 tag doesn't seem like the best fit but can't find anything better |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems solved, so:
// TODO: RUMM-1064 tag doesn't seem like the best fit but can't find anything better |
|
||
// func testWhenInterceptionIsCreated_itHasSharedAppStateListener() { | ||
// // When | ||
// let interception = TaskInterception(request: .mockAny(), isFirstParty: true) | ||
// | ||
// // Then | ||
// XCTAssert(interception.appStateListener === AppStateListener.shared) | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some leftovers, 🧽:
// func testWhenInterceptionIsCreated_itHasSharedAppStateListener() { | |
// // When | |
// let interception = TaskInterception(request: .mockAny(), isFirstParty: true) | |
// | |
// // Then | |
// XCTAssert(interception.appStateListener === AppStateListener.shared) | |
// } |
This will avoid unrealistically long spans in APM. URLSessionTracingHandler will listen to app state changes and will add foreground duration, background information to the span. PR comments addressed.
866d2a3
to
0eb6772
Compare
The problem
We are seeing spans in APM that have duration longer than hours, even days.
We realized that those are caused by the app going background and staying there for long time.
The span finishes only once the app goes back to foreground and this can result in spans longer than hours.
What and why?
The problem above happens since the app gets suspended/paused at background and picks up where it left off when it comes back to foreground.
We cannot change this behavior from our SDK, therefore we decided to provide more information so that our users can tell when their network requests take too long from when their apps stay at background for too long.
How?
We will add
foreground_duration: TimeInterval
andisBackground: Bool
tags/metrics toSpan
s.TaskInterception
will be responsible for adding these to theSpan
AppStateListener
will be responsible for listening to app state along the lifetime of interception and providingAppStateHistory
data structureforegroundDuration
andisBackground
are properties ofAppStateHistory
data structureIn this PR, we implemented 2nd and 3rd steps. In the next PR, we will implement 1st step by pluggingIt's done in this PR as it was not that major.AppStateListener
intoTaskInterception
Review checklist
TODO
TaskInterception
/URLSessionTracingHandler
needs new test casesforeground_duration
is sent as numerical value and can be queried