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:
Jorge Martin Espinosa
2025-11-19 12:42:55 +01:00
committed by GitHub
parent de3ffca1af
commit c6c2f4a267
25 changed files with 245 additions and 41 deletions

View File

@@ -90,6 +90,8 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService
import io.element.android.libraries.ui.common.nodes.emptyNode
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
@@ -136,6 +138,7 @@ class LoggedInFlowNode(
private val appPreferencesStore: AppPreferencesStore,
private val buildMeta: BuildMeta,
snackbarDispatcher: SnackbarDispatcher,
private val analyticsService: AnalyticsService,
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Placeholder,
@@ -212,6 +215,8 @@ class LoggedInFlowNode(
matrixClient.getMaxFileUploadSize()
}
analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.FirstRoomsDisplayed)
ftueService.state
.onEach { ftueState ->
when (ftueState) {

View File

@@ -18,6 +18,8 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.api.recordTransaction
import io.element.android.services.appnavstate.api.AppForegroundStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
@@ -39,6 +41,7 @@ class SyncOrchestrator(
private val appForegroundStateService: AppForegroundStateService,
private val networkMonitor: NetworkMonitor,
dispatchers: CoroutineDispatchers,
private val analyticsService: AnalyticsService,
) {
@AssistedFactory
interface Factory {
@@ -69,10 +72,13 @@ class SyncOrchestrator(
// Perform an initial sync if the sync service is not running, to check whether the homeserver is accessible
// Otherwise, if the device is offline the sync service will never start and the SyncState will be Idle, not Offline
Timber.tag(tag).d("performing initial sync attempt")
syncService.startSync()
analyticsService.recordTransaction("First sync", "syncService.startSync()") { transaction ->
syncService.startSync()
// Wait until the sync service is not idle, either it will be running or in error/offline state
syncService.syncState.first { it != SyncState.Idle }
// Wait until the sync service is not idle, either it will be running or in error/offline state
val firstState = syncService.syncState.first { it != SyncState.Idle }
transaction.setData("first_sync_state", firstState.name)
}
observeStates()
}

View File

@@ -13,6 +13,7 @@ import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
@@ -390,5 +391,6 @@ class SyncOrchestratorTest {
networkMonitor = networkMonitor,
appForegroundStateService = appForegroundStateService,
dispatchers = testCoroutineDispatchers(),
analyticsService = FakeAnalyticsService(),
)
}

View File

@@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
@@ -129,6 +130,7 @@ class MatrixSessionCacheTest {
appForegroundStateService = FakeAppForegroundStateService(),
networkMonitor = FakeNetworkMonitor(),
dispatchers = testCoroutineDispatchers(),
analyticsService = FakeAnalyticsService(),
)
}
}