mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 08:12:46 +02:00
Compare commits
4 Commits
v1.5.13
...
feature/am
Author | SHA1 | Date | |
---|---|---|---|
|
42d8bbb7b3 | ||
|
98762ef042 | ||
|
b6c0151e97 | ||
|
6e6a2fff31 |
1
changelog.d/7602.sdk
Normal file
1
changelog.d/7602.sdk
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Add sentry tracking around send message task.
|
@@ -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) {
|
||||||
|
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
@@ -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) {
|
||||||
|
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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}")
|
||||||
|
}
|
||||||
|
}
|
@@ -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]")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user