1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 08:12:46 +02:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Amit Kumar
42d8bbb7b3 Add changelog about sentry tracking 2022-11-21 18:40:47 +05:30
Amit Kumar
98762ef042 Update documentation of metrics extensions 2022-11-21 17:37:27 +05:30
Amit Kumar
b6c0151e97 Merge branch 'develop' into feature/amitkma/send-message-tracking 2022-11-17 18:43:49 +05:30
Amit Kumar
6e6a2fff31 Add sentry metrics around send event 2022-11-17 18:42:56 +05:30
12 changed files with 358 additions and 125 deletions

1
changelog.d/7602.sdk Normal file
View File

@@ -0,0 +1 @@
Add sentry tracking around send message task.

View File

@@ -25,14 +25,17 @@ import kotlin.contracts.contract
/** /**
* Executes the given [block] while measuring the transaction. * Executes the given [block] while measuring the transaction.
* *
* @param T type of the output from [block]
* @param block Action/Task to be executed within this span. * @param block Action/Task to be executed within this span.
*
* @return Output of the [block()]
*/ */
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
inline fun List<MetricPlugin>.measureMetric(block: () -> Unit) { inline fun <T> List<MetricPlugin>.measureTransaction(block: () -> T): T {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
try { return try {
this.forEach { plugin -> plugin.startTransaction() } // Start the transaction. this.forEach { plugin -> plugin.startTransaction() } // Start the transaction.
block() block()
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
@@ -46,16 +49,19 @@ inline fun List<MetricPlugin>.measureMetric(block: () -> Unit) {
/** /**
* Executes the given [block] while measuring a span. * Executes the given [block] while measuring a span.
* *
* @param T type of the output from [block]
* @param operation Name of the new span. * @param operation Name of the new span.
* @param description Description of the new span. * @param description Description of the new span.
* @param block Action/Task to be executed within this span. * @param block Action/Task to be executed within this span.
*
* @return Output of the [block]
*/ */
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
inline fun List<SpannableMetricPlugin>.measureSpan(operation: String, description: String, block: () -> Unit) { inline fun <T> List<SpannableMetricPlugin>.measureSpan(operation: String, description: String, block: () -> T): T {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
try { return try {
this.forEach { plugin -> plugin.startSpan(operation, description) } // Start the transaction. this.forEach { plugin -> plugin.startSpan(operation, description) } // Start the transaction.
block() block()
} catch (throwable: Throwable) { } catch (throwable: Throwable) {

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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
import org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber
private val loggerTag = LoggerTag("SendServiceMetricPlugin", LoggerTag.CRYPTO)
/**
* An spannable metric plugin for tracking send message, events or command task.
*/
interface SendServiceMetricPlugin : SpannableMetricPlugin {
override fun logTransaction(message: String?) {
Timber.tag(loggerTag.value).v("## sendService() : $message")
}
}

View File

@@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.extensions.measureTransaction
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel 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.CryptoDeviceInfo
@@ -355,7 +355,7 @@ internal class DeviceListManager @Inject constructor(
val relevantPlugins = metricPlugins.filterIsInstance<DownloadDeviceKeysMetricsPlugin>() val relevantPlugins = metricPlugins.filterIsInstance<DownloadDeviceKeysMetricsPlugin>()
val response: KeysQueryResponse val response: KeysQueryResponse
relevantPlugins.measureMetric { relevantPlugins.measureTransaction {
response = try { response = try {
downloadKeysForUsersTask.execute(params) downloadKeysForUsersTask.execute(params)
} catch (throwable: Throwable) { } catch (throwable: Throwable) {

View File

@@ -15,6 +15,9 @@
*/ */
package org.matrix.android.sdk.internal.crypto.tasks package org.matrix.android.sdk.internal.crypto.tasks
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.extensions.measureSpan
import org.matrix.android.sdk.api.metrics.SendServiceMetricPlugin
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
@@ -32,18 +35,23 @@ internal interface RedactEventTask : Task<RedactEventTask.Params, String> {
internal class DefaultRedactEventTask @Inject constructor( internal class DefaultRedactEventTask @Inject constructor(
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver,
matrixConfiguration: MatrixConfiguration
) : RedactEventTask { ) : RedactEventTask {
private val relevantPlugins = matrixConfiguration.metricPlugins.filterIsInstance<SendServiceMetricPlugin>()
override suspend fun execute(params: RedactEventTask.Params): String { override suspend fun execute(params: RedactEventTask.Params): String {
val response = executeRequest(globalErrorReceiver) { relevantPlugins.measureSpan("send_service.execute_redact_event_task", "Execute redact event task") {
roomAPI.redactEvent( val response = executeRequest(globalErrorReceiver) {
txId = params.txID, roomAPI.redactEvent(
roomId = params.roomId, txId = params.txID,
eventId = params.eventId, roomId = params.roomId,
reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) eventId = params.eventId,
) reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason)
)
}
return response.eventId
} }
return response.eventId
} }
} }

View File

@@ -15,6 +15,9 @@
*/ */
package org.matrix.android.sdk.internal.crypto.tasks package org.matrix.android.sdk.internal.crypto.tasks
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.extensions.measureSpan
import org.matrix.android.sdk.api.metrics.SendServiceMetricPlugin
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
@@ -41,47 +44,58 @@ internal class DefaultSendEventTask @Inject constructor(
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask, private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask,
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver,
matrixConfiguration: MatrixConfiguration
) : SendEventTask { ) : SendEventTask {
private val relevantPlugins = matrixConfiguration.metricPlugins.filterIsInstance<SendServiceMetricPlugin>()
override suspend fun execute(params: SendEventTask.Params): String { override suspend fun execute(params: SendEventTask.Params): String {
try { relevantPlugins.measureSpan("send_service.execute_send_event_task", "Execute send event task") {
if (params.event.isLocalRoomEvent) { try {
return createRoomAndSendEvent(params) if (params.event.isLocalRoomEvent) {
} return relevantPlugins.measureSpan("send_service.create_room_send_event", "Create room and send event") {
createRoomAndSendEvent(params)
// Make sure to load all members in the room before sending the event.
params.event.roomId
?.takeIf { params.encrypt }
?.let { roomId ->
try {
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
} catch (failure: Throwable) {
// send any way?
// the result is that some users won't probably be able to decrypt :/
Timber.w(failure, "SendEvent: failed to load members in room ${params.event.roomId}")
}
} }
}
val event = handleEncryption(params) // Make sure to load all members in the room before sending the event.
val localId = event.eventId!! relevantPlugins.measureSpan("send_service.load_all_members", "Load all members of room") {
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING) params.event.roomId
val response = executeRequest(globalErrorReceiver) { ?.takeIf { params.encrypt }
roomAPI.send( ?.let { roomId ->
localId, try {
roomId = event.roomId ?: "", loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
content = event.content, } catch (failure: Throwable) {
eventType = event.type ?: "" // send any way?
) // the result is that some users won't probably be able to decrypt :/
} Timber.w(failure, "SendEvent: failed to load members in room ${params.event.roomId}")
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT) }
return response.eventId.also { }
Timber.d("Event: $it just sent in ${params.event.roomId}") }
}
} catch (e: Throwable) { val event = handleEncryption(params)
val localId = event.eventId!!
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
relevantPlugins.measureSpan("send_service.room_send_event", "Send event in room") {
val response = executeRequest(globalErrorReceiver) {
roomAPI.send(
localId,
roomId = event.roomId ?: "",
content = event.content,
eventType = event.type ?: ""
)
}
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT)
return response.eventId.also {
Timber.d("Event: $it just sent in ${params.event.roomId}")
}
}
} catch (e: Throwable) {
// localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED) // localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED)
Timber.w(e, "Unable to send the Event") Timber.w(e, "Unable to send the Event")
throw e throw e
}
} }
} }
@@ -93,16 +107,18 @@ internal class DefaultSendEventTask @Inject constructor(
@Throws @Throws
private suspend fun handleEncryption(params: SendEventTask.Params): Event { private suspend fun handleEncryption(params: SendEventTask.Params): Event {
if (params.encrypt && !params.event.isEncrypted()) { relevantPlugins.measureSpan("send_service.encrypt_event", "Encrypt event") {
return encryptEventTask.execute( if (params.encrypt && !params.event.isEncrypted()) {
EncryptEventTask.Params( return encryptEventTask.execute(
params.event.roomId ?: "", EncryptEventTask.Params(
params.event, params.event.roomId ?: "",
listOf("m.relates_to") params.event,
) listOf("m.relates_to")
) )
)
}
return params.event
} }
return params.event
} }
private val Event.isLocalRoomEvent private val Event.isLocalRoomEvent

View File

@@ -25,7 +25,11 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
import org.matrix.android.sdk.api.extensions.measureSpan
import org.matrix.android.sdk.api.extensions.measureTransaction
import org.matrix.android.sdk.api.metrics.SendServiceMetricPlugin
import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@@ -73,9 +77,12 @@ internal class DefaultSendService @AssistedInject constructor(
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val eventSenderProcessor: EventSenderProcessor, private val eventSenderProcessor: EventSenderProcessor,
private val cancelSendTracker: CancelSendTracker private val cancelSendTracker: CancelSendTracker,
matrixConfiguration: MatrixConfiguration,
) : SendService { ) : SendService {
private val relevantPlugins = matrixConfiguration.metricPlugins.filterIsInstance<SendServiceMetricPlugin>()
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(roomId: String): DefaultSendService fun create(roomId: String): DefaultSendService
@@ -84,21 +91,45 @@ internal class DefaultSendService @AssistedInject constructor(
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor() private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
override fun sendEvent(eventType: String, content: JsonDict?): Cancelable { override fun sendEvent(eventType: String, content: JsonDict?): Cancelable {
return localEchoEventFactory.createEvent(roomId, eventType, content) return relevantPlugins.measureTransaction {
.also { createLocalEcho(it) } relevantPlugins.measureSpan("send_service.create_event", "Create local event") {
.let { sendEvent(it) } localEchoEventFactory.createEvent(roomId, eventType, content)
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean, additionalContent: Content?): Cancelable { override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown, additionalContent) return relevantPlugins.measureTransaction {
.also { createLocalEcho(it) } relevantPlugins.measureSpan("send_service.create_text_event", "Create local text event") {
.let { sendEvent(it) } localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown, additionalContent)
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String, additionalContent: Content?): Cancelable { override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType, additionalContent) return relevantPlugins.measureTransaction {
.also { createLocalEcho(it) } relevantPlugins.measureSpan("send_service.create_formatted_text_event", "Create local formatted text event") {
.let { sendEvent(it) } localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType, additionalContent)
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun sendQuotedTextMessage( override fun sendQuotedTextMessage(
@@ -109,35 +140,67 @@ internal class DefaultSendService @AssistedInject constructor(
rootThreadEventId: String?, rootThreadEventId: String?,
additionalContent: Content?, additionalContent: Content?,
): Cancelable { ): Cancelable {
return localEchoEventFactory.createQuotedTextEvent( return relevantPlugins.measureTransaction {
roomId = roomId, relevantPlugins.measureSpan("send_service.create_quoted_text_event", "Create local quoted text event") {
quotedEvent = quotedEvent, localEchoEventFactory.createQuotedTextEvent(
text = text, roomId = roomId,
formattedText = formattedText, quotedEvent = quotedEvent,
autoMarkdown = autoMarkdown, text = text,
rootThreadEventId = rootThreadEventId, formattedText = formattedText,
additionalContent = additionalContent, autoMarkdown = autoMarkdown,
) rootThreadEventId = rootThreadEventId,
.also { createLocalEcho(it) } additionalContent = additionalContent,
.let { sendEvent(it) } )
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun sendPoll(pollType: PollType, question: String, options: List<String>, additionalContent: Content?): Cancelable { override fun sendPoll(pollType: PollType, question: String, options: List<String>, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createPollEvent(roomId, pollType, question, options, additionalContent) return relevantPlugins.measureTransaction {
.also { createLocalEcho(it) } relevantPlugins.measureSpan("send_service.create_poll", "Create local poll event") {
.let { sendEvent(it) } localEchoEventFactory.createPollEvent(roomId, pollType, question, options, additionalContent)
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content?): Cancelable { override fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId, additionalContent) return relevantPlugins.measureTransaction {
.also { createLocalEcho(it) } relevantPlugins.measureSpan("send_service.reply_to_poll", "Create local reply poll event") {
.let { sendEvent(it) } localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId, additionalContent)
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun endPoll(pollEventId: String, additionalContent: Content?): Cancelable { override fun endPoll(pollEventId: String, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createEndPollEvent(roomId, pollEventId, additionalContent) return relevantPlugins.measureTransaction {
.also { createLocalEcho(it) } relevantPlugins.measureSpan("send_service.end_poll", "Create local end poll event") {
.let { sendEvent(it) } localEchoEventFactory.createEndPollEvent(roomId, pollEventId, additionalContent)
}.also {
relevantPlugins.measureSpan("send_service.create_local_echo", "Create local echo") {
createLocalEcho(it)
}
}.let {
relevantPlugins.measureSpan("send_service.send_event", "Send event") { sendEvent(it) }
}
}
} }
override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable { override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable {
@@ -148,11 +211,13 @@ internal class DefaultSendService @AssistedInject constructor(
} }
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable { override fun resendTextMessage(localEcho: TimelineEvent): Cancelable {
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) { relevantPlugins.measureTransaction {
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
return sendEvent(localEcho.root) localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
return relevantPlugins.measureSpan("send_service.resend_text_event", "Resend text message") { sendEvent(localEcho.root) }
}
return NoOpCancellable
} }
return NoOpCancellable
} }
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable { override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable {
@@ -163,8 +228,10 @@ internal class DefaultSendService @AssistedInject constructor(
val url = messageContent.getFileUrl() ?: return NoOpCancellable val url = messageContent.getFileUrl() ?: return NoOpCancellable
if (url.isMxcUrl()) { if (url.isMxcUrl()) {
// We need to resend only the message as the attachment is ok // We need to resend only the message as the attachment is ok
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) return relevantPlugins.measureTransaction {
return sendEvent(localEcho.root) localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
relevantPlugins.measureSpan("send_service.resend_media_message", "Resend media message") { sendEvent(localEcho.root) }
}
} }
// we need to resend the media // we need to resend the media

View File

@@ -21,10 +21,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.measureTransaction
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.getRetryDelay import org.matrix.android.sdk.api.failure.getRetryDelay
import org.matrix.android.sdk.api.failure.isLimitExceededError import org.matrix.android.sdk.api.failure.isLimitExceededError
import org.matrix.android.sdk.api.metrics.SendServiceMetricPlugin
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
@@ -58,11 +61,14 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
private val sessionParams: SessionParams, private val sessionParams: SessionParams,
private val queuedTaskFactory: QueuedTaskFactory, private val queuedTaskFactory: QueuedTaskFactory,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val memento: QueueMemento private val memento: QueueMemento,
matrixConfiguration: MatrixConfiguration,
) : EventSenderProcessor { ) : EventSenderProcessor {
private val waitForNetworkSequencer = SemaphoreCoroutineSequencer() private val waitForNetworkSequencer = SemaphoreCoroutineSequencer()
private val relevantPlugins = matrixConfiguration.metricPlugins.filterIsInstance<SendServiceMetricPlugin>()
/** /**
* sequencers use QueuedTask.queueIdentifier as key. * sequencers use QueuedTask.queueIdentifier as key.
*/ */
@@ -137,33 +143,35 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
} }
private suspend fun executeTask(task: QueuedTask) { private suspend fun executeTask(task: QueuedTask) {
try { relevantPlugins.measureTransaction {
if (task.isCancelled()) { try {
Timber.v("## $task has been cancelled, try next task") if (task.isCancelled()) {
return
}
task.waitForNetwork()
task.execute()
} catch (exception: Throwable) {
when {
exception is IOException || exception is Failure.NetworkConnection -> {
canReachServer.set(false)
task.markAsFailedOrRetry(exception, 0)
}
(exception.isLimitExceededError()) -> {
task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000))
}
exception is CancellationException -> {
Timber.v("## $task has been cancelled, try next task") Timber.v("## $task has been cancelled, try next task")
return
} }
else -> { task.waitForNetwork()
Timber.v("## un-retryable error for $task, try next task") task.execute()
// this task is in error, check next one? } catch (exception: Throwable) {
task.onTaskFailed() when {
exception is IOException || exception is Failure.NetworkConnection -> {
canReachServer.set(false)
task.markAsFailedOrRetry(exception, 0)
}
(exception.isLimitExceededError()) -> {
task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000))
}
exception is CancellationException -> {
Timber.v("## $task has been cancelled, try next task")
}
else -> {
Timber.v("## un-retryable error for $task, try next task")
// this task is in error, check next one?
task.onTaskFailed()
}
} }
} }
markAsFinished(task)
} }
markAsFinished(task)
} }
private suspend fun QueuedTask.markAsFailedOrRetry(failure: Throwable, retryDelay: Long) { private suspend fun QueuedTask.markAsFailedOrRetry(failure: Throwable, retryDelay: Long) {

View File

@@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm import io.realm.Realm
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.extensions.measureMetric
import org.matrix.android.sdk.api.extensions.measureSpan import org.matrix.android.sdk.api.extensions.measureSpan
import org.matrix.android.sdk.api.extensions.measureTransaction
import org.matrix.android.sdk.api.metrics.SyncDurationMetricPlugin import org.matrix.android.sdk.api.metrics.SyncDurationMetricPlugin
import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.pushrules.RuleScope import org.matrix.android.sdk.api.session.pushrules.RuleScope
@@ -71,7 +71,7 @@ internal class SyncResponseHandler @Inject constructor(
val isInitialSync = fromToken == null val isInitialSync = fromToken == null
Timber.v("Start handling sync, is InitialSync: $isInitialSync") Timber.v("Start handling sync, is InitialSync: $isInitialSync")
relevantPlugins.measureMetric { relevantPlugins.measureTransaction {
startCryptoService(isInitialSync) startCryptoService(isInitialSync)
// Handle the to device events before the room ones // Handle the to device events before the room ones

View File

@@ -17,6 +17,7 @@
package im.vector.app.features.analytics.metrics package im.vector.app.features.analytics.metrics
import im.vector.app.features.analytics.metrics.sentry.SentryDownloadDeviceKeysMetrics import im.vector.app.features.analytics.metrics.sentry.SentryDownloadDeviceKeysMetrics
import im.vector.app.features.analytics.metrics.sentry.SentrySendServiceMetrics
import im.vector.app.features.analytics.metrics.sentry.SentrySyncDurationMetrics import im.vector.app.features.analytics.metrics.sentry.SentrySyncDurationMetrics
import org.matrix.android.sdk.api.metrics.MetricPlugin import org.matrix.android.sdk.api.metrics.MetricPlugin
import javax.inject.Inject import javax.inject.Inject
@@ -29,9 +30,10 @@ import javax.inject.Singleton
data class VectorPlugins @Inject constructor( data class VectorPlugins @Inject constructor(
val sentryDownloadDeviceKeysMetrics: SentryDownloadDeviceKeysMetrics, val sentryDownloadDeviceKeysMetrics: SentryDownloadDeviceKeysMetrics,
val sentrySyncDurationMetrics: SentrySyncDurationMetrics, val sentrySyncDurationMetrics: SentrySyncDurationMetrics,
val sentrySendServiceMetrics: SentrySendServiceMetrics
) { ) {
/** /**
* Returns [List] of all [MetricPlugin] hold by this class. * Returns [List] of all [MetricPlugin] hold by this class.
*/ */
fun plugins(): List<MetricPlugin> = listOf(sentryDownloadDeviceKeysMetrics, sentrySyncDurationMetrics) fun plugins(): List<MetricPlugin> = listOf(sentryDownloadDeviceKeysMetrics, sentrySyncDurationMetrics, sentrySendServiceMetrics)
} }

View File

@@ -0,0 +1,91 @@
/*
* 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.ISpan
import io.sentry.ITransaction
import io.sentry.Sentry
import io.sentry.SpanStatus
import org.matrix.android.sdk.api.metrics.SendServiceMetricPlugin
import java.util.EmptyStackException
import java.util.Stack
import javax.inject.Inject
/**
* Sentry based implementation of SyncDurationMetricPlugin.
*/
class SentrySendServiceMetrics @Inject constructor() : SendServiceMetricPlugin {
private var transaction: ITransaction? = null
// Stacks to keep spans in LIFO order.
private var spans: Stack<ISpan> = Stack()
/**
* Starts the span for a sub-task.
*
* @param operation Name of the new span.
* @param description Description of the new span.
*
* @throws IllegalStateException if this is called without starting a transaction ie. `measureSpan` must be called within `measureMetric`.
*/
override fun startSpan(operation: String, description: String) {
if (Sentry.isEnabled()) {
val span = Sentry.getSpan() ?: transaction
span?.let {
val innerSpan = it.startChild(operation, description)
spans.push(innerSpan)
logTransaction("Sentry span started: operation=[$operation], description=[$description]")
}
}
}
override fun finishSpan() {
try {
spans.pop()
} catch (e: EmptyStackException) {
null
}?.finish()
logTransaction("Sentry span finished")
}
override fun startTransaction() {
if (Sentry.isEnabled()) {
transaction = Sentry.startTransaction("SendService", "action.handle", true)
logTransaction("Sentry transaction started")
}
}
override fun finishTransaction() {
transaction?.finish()
logTransaction("Sentry transaction finished")
}
override fun onError(throwable: Throwable) {
try {
spans.peek()
} catch (e: EmptyStackException) {
null
}?.apply {
this.throwable = throwable
this.status = SpanStatus.INTERNAL_ERROR
} ?: transaction?.apply {
this.throwable = throwable
this.status = SpanStatus.INTERNAL_ERROR
}
logTransaction("Sentry transaction encountered error ${throwable.message}")
}
}

View File

@@ -44,10 +44,12 @@ class SentrySyncDurationMetrics @Inject constructor() : SyncDurationMetricPlugin
*/ */
override fun startSpan(operation: String, description: String) { override fun startSpan(operation: String, description: String) {
if (Sentry.isEnabled()) { if (Sentry.isEnabled()) {
val span = Sentry.getSpan() ?: throw IllegalStateException("measureSpan block must be called within measureMetric") val span = Sentry.getSpan()
val innerSpan = span.startChild(operation, description) span?.let {
spans.push(innerSpan) val innerSpan = it.startChild(operation, description)
logTransaction("Sentry span started: operation=[$operation], description=[$description]") spans.push(innerSpan)
logTransaction("Sentry span started: operation=[$operation], description=[$description]")
}
} }
} }