Skip to content
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

Add metrics plugin to track device download keys task #7438

Merged
merged 8 commits into from
Nov 2, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package org.matrix.android.sdk.api
import okhttp3.ConnectionSpec
import okhttp3.Interceptor
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.metrics.MetricsPlugin
import org.matrix.android.sdk.api.metrics.impl.NoOpMetricsPlugin
import java.net.Proxy

data class MatrixConfiguration(
Expand Down Expand Up @@ -74,4 +76,9 @@ data class MatrixConfiguration(
* Sync configuration.
*/
val syncConfig: SyncConfig = SyncConfig(),

/**
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
*/
val metricsPlugin: MetricsPlugin = NoOpMetricsPlugin()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.extensions

import org.matrix.android.sdk.api.metrics.MetricsPlugin
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Executes the given [block] while measuring the transaction.
*/
@OptIn(ExperimentalContracts::class)
public inline fun <T> measureMetric(metricMeasurementPlugin: MetricsPlugin, block: () -> T): T {
contract {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's contract? Do we really need it (i can see it's experimental)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is basically a way to tell the compiler about how the function behaves. Sometimes the compiler doesn't know or is not fully aware of certain conditions or returns. So you can specify an effect when a function is invoked to help the compiler.
In our case, it allows us to write this code:

val response: KeysQueryResponse 

measureMetric(plugin) {
    response = block() // this won't be possible without contracts.
}

callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
metricMeasurementPlugin.startTransaction()
val answer = block()
amitkma marked this conversation as resolved.
Show resolved Hide resolved
metricMeasurementPlugin.finishTransaction()
return answer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.metrics

interface DownloadDeviceKeysMetricsPlugin: MetricsPlugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.metrics

/**
* A plugin that can be used to capture metrics in Client.
*/
interface MetricsPlugin {
/**
* Start the measurement of the metrics as soon as task is started.
*/
fun startTransaction()

/**
* Mark the measuring transaction finished once the task is completed.
*/
fun finishTransaction()

fun onError(throwable: Throwable)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.metrics.impl

import org.matrix.android.sdk.api.metrics.MetricsPlugin

/**
* A no-op implementation of metrics plugin. This class simply bypasses all the transaction methods.
*/
class NoOpMetricsPlugin: MetricsPlugin {
override fun startTransaction() {
// No-op
}

override fun finishTransaction() {
// No-op
}

override fun onError(throwable: Throwable) {
// No-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ package org.matrix.android.sdk.internal.crypto

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.extensions.measureMetric
import org.matrix.android.sdk.api.metrics.MetricsPlugin
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
Expand All @@ -47,8 +50,11 @@ internal class DeviceListManager @Inject constructor(
coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor,
private val clock: Clock,
matrixConfiguration: MatrixConfiguration
) {

private val metricsPlugin = matrixConfiguration.metricsPlugin

interface UserDevicesUpdateListener {
fun onUsersDeviceUpdate(userIds: List<String>)
}
Expand Down Expand Up @@ -345,19 +351,26 @@ internal class DeviceListManager @Inject constructor(
return MXUsersDevicesMap()
}
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
val response = try {
downloadKeysForUsersTask.execute(params)
} catch (throwable: Throwable) {
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
if (throwable is CancellationException) {
// the crypto module is getting closed, so we cannot access the DB anymore
Timber.w("The crypto module is closed, ignoring this error")
} else {
onKeysDownloadFailed(filteredUsers)

val response = measureMetric(metricsPlugin) {
val result = try {
downloadKeysForUsersTask.execute(params)
}
throw throwable
catch (throwable: Throwable) {
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
if (throwable is CancellationException) {
// the crypto module is getting closed, so we cannot access the DB anymore
Timber.w("The crypto module is closed, ignoring this error")
} else {
onKeysDownloadFailed(filteredUsers)
}
metricsPlugin.onError(throwable)
throw throwable
}
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
return@measureMetric result
}
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")

for (userId in filteredUsers) {
// al devices =
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import im.vector.app.core.utils.SystemSettingsProvider
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
import im.vector.app.features.analytics.metrics.VectorMetricsPlugin
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
import im.vector.app.features.navigation.DefaultNavigator
Expand All @@ -70,6 +71,7 @@ import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.metrics.MetricsPlugin
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
Expand Down Expand Up @@ -117,6 +119,9 @@ abstract class VectorBindModule {

@Binds
abstract fun bindGetDeviceInfoUseCase(getDeviceInfoUseCase: DefaultGetDeviceInfoUseCase): GetDeviceInfoUseCase

@Binds
abstract fun bindMetricsPlugin(vectorMetricsPlugin: VectorMetricsPlugin): MetricsPlugin
}

@InstallIn(SingletonComponent::class)
Expand All @@ -143,14 +148,16 @@ object VectorStaticModule {
vectorPreferences: VectorPreferences,
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
flipperProxy: FlipperProxy,
metricsPlugin: MetricsPlugin,
): MatrixConfiguration {
return MatrixConfiguration(
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
networkInterceptors = listOfNotNull(
flipperProxy.networkInterceptor(),
)
),
metricsPlugin = metricsPlugin
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.analytics.metrics

import im.vector.app.features.analytics.metrics.sentry.SentryDownloadDeviceKeysMetrics
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
import javax.inject.Inject
import javax.inject.Singleton

/**
* Default implementation of MetricsPlugin.
*/
@Singleton
class VectorMetricsPlugin @Inject constructor(
private val sentryDownloadDeviceKeysMetrics: SentryDownloadDeviceKeysMetrics
) : DownloadDeviceKeysMetricsPlugin by sentryDownloadDeviceKeysMetrics
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.analytics.metrics.sentry

import io.sentry.ITransaction
import io.sentry.Sentry
import io.sentry.SpanStatus
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
import javax.inject.Inject

class SentryDownloadDeviceKeysMetrics @Inject constructor(): DownloadDeviceKeysMetricsPlugin {
private var transaction: ITransaction? = null

override fun startTransaction() {
transaction = Sentry.startTransaction("download_device_keys", "task");
}

override fun finishTransaction() {
transaction?.finish()
}

override fun onError(throwable: Throwable) {
transaction?.apply {
this.throwable = throwable
this.status = SpanStatus.INTERNAL_ERROR
}
}
}