Skip to content

Commit b674665

Browse files
authored
Add metrics plugin to track device download keys task (#7438)
* Add metrics tracking plugin for download device keys * Add support for multiple metrics plugin * Update copyright license header in matrix-sdk-android * Add tests for MetricExtension * Update changelog * Improve MetricsExtension and reformatting
1 parent 646cc7d commit b674665

File tree

9 files changed

+236
-18
lines changed

9 files changed

+236
-18
lines changed

changelog.d/7438.sdk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add MetricPlugin interface to implement metrics in SDK clients.

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.api
1919
import okhttp3.ConnectionSpec
2020
import okhttp3.Interceptor
2121
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
22+
import org.matrix.android.sdk.api.metrics.MetricPlugin
2223
import java.net.Proxy
2324

2425
data class MatrixConfiguration(
@@ -74,4 +75,9 @@ data class MatrixConfiguration(
7475
* Sync configuration.
7576
*/
7677
val syncConfig: SyncConfig = SyncConfig(),
78+
79+
/**
80+
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
81+
*/
82+
val metricPlugins: List<MetricPlugin> = emptyList()
7783
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.api.extensions
18+
19+
import org.matrix.android.sdk.api.metrics.MetricPlugin
20+
import kotlin.contracts.ExperimentalContracts
21+
import kotlin.contracts.InvocationKind
22+
import kotlin.contracts.contract
23+
24+
/**
25+
* Executes the given [block] while measuring the transaction.
26+
*/
27+
@OptIn(ExperimentalContracts::class)
28+
inline fun measureMetric(metricMeasurementPlugins: List<MetricPlugin>, block: () -> Unit) {
29+
contract {
30+
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
31+
}
32+
try {
33+
metricMeasurementPlugins.forEach { plugin -> plugin.startTransaction() } // Start the transaction.
34+
block()
35+
} catch (throwable: Throwable) {
36+
metricMeasurementPlugins.forEach { plugin -> plugin.onError(throwable) } // Capture if there is any exception thrown.
37+
throw throwable
38+
} finally {
39+
metricMeasurementPlugins.forEach { plugin -> plugin.finishTransaction() } // Finally, finish this transaction.
40+
}
41+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.api.metrics
18+
19+
import org.matrix.android.sdk.api.logger.LoggerTag
20+
import timber.log.Timber
21+
22+
private val loggerTag = LoggerTag("DownloadKeysMetricsPlugin", LoggerTag.CRYPTO)
23+
24+
/**
25+
* Extension of MetricPlugin for download_device_keys task.
26+
*/
27+
interface DownloadDeviceKeysMetricsPlugin : MetricPlugin {
28+
29+
override fun logTransaction(message: String?) {
30+
Timber.tag(loggerTag.value).v("## downloadDeviceKeysMetricPlugin() : $message")
31+
}
32+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.api.metrics
18+
19+
/**
20+
* A plugin that can be used to capture metrics in Client.
21+
*/
22+
interface MetricPlugin {
23+
/**
24+
* Start the measurement of the metrics as soon as task is started.
25+
*/
26+
fun startTransaction()
27+
28+
/**
29+
* Mark the measuring transaction finished once the task is completed.
30+
*/
31+
fun finishTransaction()
32+
33+
/**
34+
* Invoked when there is any error in the ongoing task. The metrics tool can use this information to attach to the ongoing transaction.
35+
*
36+
* @param throwable Exception thrown in the running task.
37+
*/
38+
fun onError(throwable: Throwable)
39+
40+
/**
41+
* Can be used to log this transaction.
42+
*/
43+
fun logTransaction(message: String? = "") {
44+
// no-op
45+
}
46+
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ package org.matrix.android.sdk.internal.crypto
1818

1919
import kotlinx.coroutines.CancellationException
2020
import kotlinx.coroutines.launch
21+
import org.matrix.android.sdk.api.MatrixConfiguration
2122
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
2223
import org.matrix.android.sdk.api.MatrixPatterns
2324
import org.matrix.android.sdk.api.auth.data.Credentials
25+
import org.matrix.android.sdk.api.extensions.measureMetric
26+
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
2427
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
2528
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
2629
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
2730
import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper
31+
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
2832
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
2933
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
3034
import org.matrix.android.sdk.internal.session.SessionScope
@@ -47,8 +51,11 @@ internal class DeviceListManager @Inject constructor(
4751
coroutineDispatchers: MatrixCoroutineDispatchers,
4852
private val taskExecutor: TaskExecutor,
4953
private val clock: Clock,
54+
matrixConfiguration: MatrixConfiguration
5055
) {
5156

57+
private val metricPlugins = matrixConfiguration.metricPlugins
58+
5259
interface UserDevicesUpdateListener {
5360
fun onUsersDeviceUpdate(userIds: List<String>)
5461
}
@@ -345,19 +352,25 @@ internal class DeviceListManager @Inject constructor(
345352
return MXUsersDevicesMap()
346353
}
347354
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
348-
val response = try {
349-
downloadKeysForUsersTask.execute(params)
350-
} catch (throwable: Throwable) {
351-
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
352-
if (throwable is CancellationException) {
353-
// the crypto module is getting closed, so we cannot access the DB anymore
354-
Timber.w("The crypto module is closed, ignoring this error")
355-
} else {
356-
onKeysDownloadFailed(filteredUsers)
355+
val relevantPlugins = metricPlugins.filterIsInstance<DownloadDeviceKeysMetricsPlugin>()
356+
357+
val response: KeysQueryResponse
358+
measureMetric(relevantPlugins) {
359+
response = try {
360+
downloadKeysForUsersTask.execute(params)
361+
} catch (throwable: Throwable) {
362+
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
363+
if (throwable is CancellationException) {
364+
// the crypto module is getting closed, so we cannot access the DB anymore
365+
Timber.w("The crypto module is closed, ignoring this error")
366+
} else {
367+
onKeysDownloadFailed(filteredUsers)
368+
}
369+
throw throwable
357370
}
358-
throw throwable
371+
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
359372
}
360-
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
373+
361374
for (userId in filteredUsers) {
362375
// al devices =
363376
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }

vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import im.vector.app.core.utils.SystemSettingsProvider
4747
import im.vector.app.features.analytics.AnalyticsTracker
4848
import im.vector.app.features.analytics.VectorAnalytics
4949
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
50+
import im.vector.app.features.analytics.metrics.VectorPlugins
5051
import im.vector.app.features.invite.AutoAcceptInvites
5152
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
5253
import im.vector.app.features.navigation.DefaultNavigator
@@ -75,9 +76,7 @@ import org.matrix.android.sdk.api.session.Session
7576
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
7677
import javax.inject.Singleton
7778

78-
@InstallIn(SingletonComponent::class)
79-
@Module
80-
abstract class VectorBindModule {
79+
@InstallIn(SingletonComponent::class) @Module abstract class VectorBindModule {
8180

8281
@Binds
8382
abstract fun bindNavigator(navigator: DefaultNavigator): Navigator
@@ -119,9 +118,7 @@ abstract class VectorBindModule {
119118
abstract fun bindGetDeviceInfoUseCase(getDeviceInfoUseCase: DefaultGetDeviceInfoUseCase): GetDeviceInfoUseCase
120119
}
121120

122-
@InstallIn(SingletonComponent::class)
123-
@Module
124-
object VectorStaticModule {
121+
@InstallIn(SingletonComponent::class) @Module object VectorStaticModule {
125122

126123
@Provides
127124
fun providesContext(application: Application): Context {
@@ -143,14 +140,16 @@ object VectorStaticModule {
143140
vectorPreferences: VectorPreferences,
144141
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
145142
flipperProxy: FlipperProxy,
143+
vectorPlugins: VectorPlugins,
146144
): MatrixConfiguration {
147145
return MatrixConfiguration(
148146
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
149147
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
150148
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
151149
networkInterceptors = listOfNotNull(
152150
flipperProxy.networkInterceptor(),
153-
)
151+
),
152+
metricPlugins = vectorPlugins.plugins(),
154153
)
155154
}
156155

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.analytics.metrics
18+
19+
import im.vector.app.features.analytics.metrics.sentry.SentryDownloadDeviceKeysMetrics
20+
import org.matrix.android.sdk.api.metrics.MetricPlugin
21+
import javax.inject.Inject
22+
import javax.inject.Singleton
23+
24+
/**
25+
* Class that contains the all plugins which can be used for tracking.
26+
*/
27+
@Singleton
28+
data class VectorPlugins @Inject constructor(
29+
val sentryDownloadDeviceKeysMetrics: SentryDownloadDeviceKeysMetrics,
30+
) {
31+
/**
32+
* Returns [List] of all [MetricPlugin] hold by this class.
33+
*/
34+
fun plugins(): List<MetricPlugin> = listOf(sentryDownloadDeviceKeysMetrics)
35+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.analytics.metrics.sentry
18+
19+
import io.sentry.ITransaction
20+
import io.sentry.Sentry
21+
import io.sentry.SpanStatus
22+
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
23+
import javax.inject.Inject
24+
25+
class SentryDownloadDeviceKeysMetrics @Inject constructor() : DownloadDeviceKeysMetricsPlugin {
26+
private var transaction: ITransaction? = null
27+
28+
override fun startTransaction() {
29+
transaction = Sentry.startTransaction("download_device_keys", "task")
30+
logTransaction("Sentry transaction started")
31+
}
32+
33+
override fun finishTransaction() {
34+
transaction?.finish()
35+
logTransaction("Sentry transaction finished")
36+
}
37+
38+
override fun onError(throwable: Throwable) {
39+
transaction?.apply {
40+
this.throwable = throwable
41+
this.status = SpanStatus.INTERNAL_ERROR
42+
}
43+
logTransaction("Sentry transaction encountered error ${throwable.message}")
44+
}
45+
}

0 commit comments

Comments
 (0)