Add some performance metrics for Sentry (#5760)
- Add `AnalyticsService.startTransaction(...)` to start a logging transaction that can be uploaded to Sentry if the user enabled the analytics upload. - Add `AnalyticsTransaction` wrapper to abstract the Sentry ones. - Added several helper methods to improve the UX around these transactions. - Then measure: - Time until the first sync, and how it ended. - Time until the first rooms are displayed. - Time to load a room or a preview. - Time to load a timeline.
This commit is contained in:
parent
c8604c262a
commit
f78c80803b
25 changed files with 245 additions and 41 deletions
|
|
@ -76,6 +76,7 @@ import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
|||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -131,6 +132,7 @@ class RustMatrixClient(
|
|||
clock: SystemClock,
|
||||
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : MatrixClient {
|
||||
override val sessionId: UserId = UserId(innerClient.userId())
|
||||
override val deviceId: DeviceId = DeviceId(innerClient.deviceId())
|
||||
|
|
@ -178,6 +180,7 @@ class RustMatrixClient(
|
|||
roomListFactory = RoomListFactory(
|
||||
innerRoomListService = innerRoomListService,
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
analyticsService = analyticsService,
|
||||
),
|
||||
roomSyncSubscriber = roomSyncSubscriber,
|
||||
)
|
||||
|
|
@ -212,6 +215,7 @@ class RustMatrixClient(
|
|||
roomMembershipObserver = roomMembershipObserver,
|
||||
roomInfoMapper = roomInfoMapper,
|
||||
featureFlagService = featureFlagService,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
|
||||
override val matrixMediaLoader: MatrixMediaLoader = RustMediaLoader(
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class RustMatrixClientFactory(
|
|||
clock = clock,
|
||||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
||||
featureFlagService = featureFlagService,
|
||||
analyticsService = analyticsService,
|
||||
).also {
|
||||
Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
|||
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
|
||||
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
||||
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.recordTransaction
|
||||
import io.element.android.services.analyticsproviders.api.recordChildTransaction
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
|
|
@ -54,6 +57,7 @@ class RustRoomFactory(
|
|||
private val featureFlagService: FeatureFlagService,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val roomInfoMapper: RoomInfoMapper,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) {
|
||||
private val dispatcher = dispatchers.io.limitedParallelism(1)
|
||||
private val mutex = Mutex()
|
||||
|
|
@ -106,48 +110,64 @@ class RustRoomFactory(
|
|||
Timber.d("Room factory is destroyed, returning null for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withContext null
|
||||
|
||||
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withLock null
|
||||
|
||||
if (sdkRoom.membership() == Membership.JOINED) {
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
// Init the live timeline in the SDK from the Room
|
||||
val timeline = sdkRoom.timelineWithConfiguration(
|
||||
TimelineConfiguration(
|
||||
focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents),
|
||||
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
|
||||
internalIdPrefix = "live",
|
||||
dateDividerMode = DateDividerMode.DAILY,
|
||||
trackReadReceipts = true,
|
||||
reportUtds = true,
|
||||
)
|
||||
)
|
||||
analyticsService.recordTransaction(
|
||||
name = "Get joined room",
|
||||
operation = "RustRoomFactory.getJoinedRoomOrPreview",
|
||||
) { transaction ->
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
// Init the live timeline in the SDK from the Room
|
||||
val timeline = transaction.recordChildTransaction(
|
||||
operation = "sdkRoom.timelineWithConfiguration",
|
||||
description = "Get timeline from the SDK",
|
||||
) {
|
||||
sdkRoom.timelineWithConfiguration(
|
||||
TimelineConfiguration(
|
||||
focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents),
|
||||
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
|
||||
internalIdPrefix = "live",
|
||||
dateDividerMode = DateDividerMode.DAILY,
|
||||
trackReadReceipts = true,
|
||||
reportUtds = true,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
GetRoomResult.Joined(
|
||||
JoinedRustRoom(
|
||||
baseRoom = getBaseRoom(sdkRoom),
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
liveInnerTimeline = timeline,
|
||||
coroutineDispatchers = dispatchers,
|
||||
systemClock = systemClock,
|
||||
featureFlagService = featureFlagService,
|
||||
GetRoomResult.Joined(
|
||||
JoinedRustRoom(
|
||||
baseRoom = getBaseRoom(sdkRoom),
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
liveInnerTimeline = timeline,
|
||||
coroutineDispatchers = dispatchers,
|
||||
systemClock = systemClock,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val preview = try {
|
||||
sdkRoom.previewRoom(via = serverNames)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get room preview for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
} else {
|
||||
analyticsService.recordTransaction(
|
||||
name = "Get preview of room",
|
||||
operation = "RustRoomFactory.getJoinedRoomOrPreview",
|
||||
) {
|
||||
val preview = try {
|
||||
sdkRoom.previewRoom(via = serverNames)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get room preview for $roomId")
|
||||
return@recordTransaction null
|
||||
}
|
||||
|
||||
GetRoomResult.NotJoined(
|
||||
NotJoinedRustRoom(
|
||||
sessionId = sessionId,
|
||||
localRoom = getBaseRoom(sdkRoom),
|
||||
previewInfo = RoomPreviewInfoMapper.map(preview.info()),
|
||||
GetRoomResult.NotJoined(
|
||||
NotJoinedRustRoom(
|
||||
sessionId = sessionId,
|
||||
localRoom = getBaseRoom(sdkRoom),
|
||||
previewInfo = RoomPreviewInfoMapper.map(preview.info()),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -36,6 +38,7 @@ private val ROOM_LIST_RUST_FILTERS = listOf(
|
|||
internal class RoomListFactory(
|
||||
private val innerRoomListService: RoomListService,
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) {
|
||||
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory()
|
||||
|
||||
|
|
@ -59,6 +62,8 @@ internal class RoomListFactory(
|
|||
val loadedPages = MutableStateFlow(1)
|
||||
var innerRoomList: InnerRoomList? = null
|
||||
|
||||
val firstRoomsTransaction = analyticsService.startTransaction("Load first set of rooms", "innerRoomList.entriesFlow")
|
||||
|
||||
coroutineScope.launch(coroutineContext) {
|
||||
innerRoomList = innerProvider()
|
||||
innerRoomList.let { innerRoomList ->
|
||||
|
|
@ -67,6 +72,10 @@ internal class RoomListFactory(
|
|||
roomListDynamicEvents = dynamicEvents,
|
||||
initialFilterKind = RoomListEntriesDynamicFilterKind.All(ROOM_LIST_RUST_FILTERS),
|
||||
).onEach { update ->
|
||||
if (!firstRoomsTransaction.isFinished()) {
|
||||
analyticsService.stopLongRunningTransaction(AnalyticsLongRunningTransaction.FirstRoomsDisplayed)
|
||||
firstRoomsTransaction.finish()
|
||||
}
|
||||
processor.postUpdate(update)
|
||||
}.launchIn(this)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import org.matrix.rustcomponents.sdk.SyncService as InnerSyncService
|
|||
class RustSyncService(
|
||||
private val inner: InnerSyncService,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
sessionCoroutineScope: CoroutineScope
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
) : SyncService {
|
||||
private val isServiceReady = AtomicBoolean(true)
|
||||
|
||||
|
|
@ -71,10 +71,10 @@ class RustSyncService(
|
|||
override val syncState: StateFlow<SyncState> =
|
||||
inner.stateFlow()
|
||||
.map(SyncServiceState::toSyncState)
|
||||
.distinctUntilChanged()
|
||||
.onEach { state ->
|
||||
Timber.i("Sync state=$state")
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle)
|
||||
|
||||
override val isOnline: StateFlow<Boolean> = syncState.mapState { it != SyncState.Offline }
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ private fun RustOtherState.map(): OtherState {
|
|||
RustOtherState.RoomEncryption -> OtherState.RoomEncryption
|
||||
RustOtherState.RoomGuestAccess -> OtherState.RoomGuestAccess
|
||||
RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility
|
||||
RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules
|
||||
is RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules
|
||||
is RustOtherState.RoomName -> OtherState.RoomName(name)
|
||||
is RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents(change.map())
|
||||
is RustOtherState.RoomPowerLevels -> OtherState.RoomUserPowerLevels(users)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME
|
|||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
|
|
@ -116,5 +117,6 @@ class RustMatrixClientTest {
|
|||
clock = FakeSystemClock(),
|
||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.roomlist
|
|||
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomList
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
|
@ -22,6 +23,7 @@ class RoomListFactoryTest {
|
|||
val sut = RoomListFactory(
|
||||
innerRoomListService = FakeFfiRoomListService(),
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
)
|
||||
sut.createRoomList(
|
||||
pageSize = 10,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService
|
||||
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
|
|
@ -52,6 +53,7 @@ private fun TestScope.createRustRoomListService(
|
|||
roomListFactory = RoomListFactory(
|
||||
innerRoomListService = roomListService,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
),
|
||||
roomSyncSubscriber = RoomSyncSubscriber(
|
||||
roomListService = roomListService,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue