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

Compare commits

...

9 Commits

Author SHA1 Message Date
Valere
192b0ed3fb change way to compute local echo 2020-10-08 14:37:12 +02:00
Valere
c909fc50a2 Perf Tracing server support 2020-10-05 18:44:42 +02:00
ganfra
5cf1175d7f Merge branch 'develop' into feature/performance_tracking 2020-10-01 17:29:22 +02:00
ganfra
7be93e03ba Profiler: clean code 2020-10-01 17:20:51 +02:00
ganfra
98b86e977f Profile send: add one more stage 2020-10-01 16:38:14 +02:00
ganfra
3cb2873f93 Add logs for timeline build in debug 2020-10-01 15:33:57 +02:00
ganfra
bb7cd409e6 Profiler: make it generic so you can use it as you want. 2020-10-01 15:33:48 +02:00
ganfra
cef191f1a5 Performance tracking: add more stages in encryption 2020-09-30 19:20:38 +02:00
ganfra
2ab2c5c94b Performance tracker: start implementing send tracking 2020-09-29 21:07:58 +02:00
30 changed files with 537 additions and 36 deletions

View File

@@ -56,11 +56,19 @@ android {
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
// Set to BODY instead of NONE to enable logging
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level." + project.property("vector.httpLogLevel")
buildConfigField "String", "PERF_TRACING_SERVER", project.property("vector.perfTracingServer")
buildConfigField "String", "PERF_TRACING_SERVER_TOKEN", project.property("vector.perfTracingServerToken")
buildConfigField "String", "PERF_TRACING_SERVER_USER", project.property("vector.perfTracingServerUser")
}
release {
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
buildConfigField "String", "PERF_TRACING_SERVER", ""
buildConfigField "String", "PERF_TRACING_SERVER_TOKEN", ""
buildConfigField "String", "PERF_TRACING_SERVER_USER", ""
}
}
@@ -169,6 +177,9 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
debugImplementation 'com.nikitakozlov.pury:pury:1.1.0'
releaseImplementation 'com.nikitakozlov.pury:pury-no-op:1.1.0'
// Bus
implementation 'org.greenrobot:eventbus:3.1.1'

View File

@@ -21,11 +21,13 @@ import android.content.Context
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration
import androidx.work.WorkManager
import com.nikitakozlov.pury.Pury
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.util.profiling.PerfServerPlugin
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
import org.matrix.android.sdk.internal.network.UserAgentHolder
@@ -48,6 +50,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var perfServerPlugin: PerfServerPlugin
init {
Monarchy.init(context)
@@ -56,6 +59,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
}
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
if (!BuildConfig.PERF_TRACING_SERVER.isNullOrEmpty()) {
Pury.addPlugin("per-server", perfServerPlugin)
}
}
fun getUserAgent() = userAgentHolder.userAgent

View File

@@ -112,7 +112,8 @@ interface CryptoService {
fun isRoomEncrypted(roomId: String): Boolean
fun encryptEventContent(eventContent: Content,
fun encryptEventContent(eventId: String,
eventContent: Content,
eventType: String,
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>)

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 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.session.room.send
import org.matrix.android.sdk.api.util.profiling.BaseProfiler
object SendPerformanceProfiler: BaseProfiler<SendPerformanceProfiler.Stages>() {
enum class Stages() {
// LOCAL_ECHO,
ENCRYPT_WORKER,
ENCRYPT_GET_USERS,
ENCRYPT_SET_ROOM_ENCRYPTION,
ENCRYPT_MEGOLM_SHARE_KEYS,
ENCRYPT_MEGOLM_ENCRYPT,
SEND_WORKER,
GET_UP_TO_DATE_ECHO,
SEND_REQUEST,
RECEIVED_IN_SYNC
}
override val name = "SEND_PROFILER"
}

View File

@@ -34,9 +34,10 @@ interface SendService {
* Method to send a generic event asynchronously. If you want to send a state event, please use [StateService] instead.
* @param eventType the type of the event
* @param content the optional body as a json dict.
* @param onBuiltEvent lambda to react to the event creation
* @return a [Cancelable]
*/
fun sendEvent(eventType: String, content: Content?): Cancelable
fun sendEvent(eventType: String, content: Content?, onBuiltEvent: ((Event) -> Unit)? = null): Cancelable
/**
* Method to send a text message asynchronously.
@@ -47,7 +48,12 @@ interface SendService {
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
* @return a [Cancelable]
*/
fun sendTextMessage(text: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable
fun sendTextMessage(
text: CharSequence,
msgType: String = MessageType.MSGTYPE_TEXT,
autoMarkdown: Boolean = false,
onBuiltEvent: ((Event) -> Unit)? = null
): Cancelable
/**
* Method to send a text message with a formatted body.
@@ -56,7 +62,12 @@ interface SendService {
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @return a [Cancelable]
*/
fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
fun sendFormattedTextMessage(
text: String,
formattedText: String,
msgType: String = MessageType.MSGTYPE_TEXT,
onBuiltEvent: ((Event) -> Unit)? = null
): Cancelable
/**
* Method to send a media asynchronously.

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2020 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.util.profiling
import com.nikitakozlov.pury.Pury
private const val ROOT_STAGE = "ROOT_STAGE"
/**
* Base class for profilers, it's implementing Pury start and stop profiling.
* Order of STAGE in enum matters.
*/
abstract class BaseProfiler<STAGE : Enum<STAGE>> {
private val currentProfilers = ArrayList<String>()
abstract val name: String
/**
* Use to start the profiling process. It's necessary to call this method before any other method.
* You should always call stop profiling with the same key param when you want to stop profiling.
*
* @param key unique identifier of the profiling.
*/
fun startProfiling(key: String) {
if (currentProfilers.contains(key)) {
return
}
currentProfilers.add(key)
Pury.startProfiling(profilerName(key), ROOT_STAGE, 0, 1)
}
/**
* Use to stop the profiling process. This will dispatch information to the configured pury plugins.
*
* @param key unique identifier of the profiling.
*/
fun stopProfiling(key: String) {
if (!currentProfilers.contains(key)) {
return
}
Pury.stopProfiling(profilerName(key), ROOT_STAGE, 1)
currentProfilers.remove(key)
}
/**
* Use to profile a block of code. Internally it will call start and stop stage.
*
* @param key unique identifier of the profiling.
* @param stage the current stage to mark
*/
fun profileBlock(key: String, stage: STAGE, block: (() -> Unit)? = null) {
startStage(key, stage)
block?.invoke()
stopStage(key, stage)
}
/**
* Use to add a new stage to profile inside the root profiling.
* You should have called startProfiling with the same key before.
* You should also call stopStage with same key and same stage to mark the end of this stage.
*
* @param key unique identifier of the profiling.
* @param stage the current stage to profile
*/
fun startStage(key: String, stage: STAGE) {
if (!currentProfilers.contains(key)) return
Pury.startProfiling(profilerName(key), stage.name, stage.ordinal + 1, 1)
}
/**
* Use to finish the profiling of a stage.
* You should have called startStage with the same key and same stage before.
*
* @param key unique identifier of the profiling.
* @param stage the current stage to profile
*/
fun stopStage(key: String, stage: STAGE) {
if (!currentProfilers.contains(key)) return
Pury.stopProfiling(profilerName(key), stage.name, 1)
}
private fun profilerName(key: String) = "${name}_$key"
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2020 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.util.profiling
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST
interface PerServerAPI {
@POST("api/result")
fun postReport(@Body profileResult: ProfileReport): Call<Unit>
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 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.util.profiling
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
interface ProfileResult {
val name: String?
val depth: Int?
val nestedResults: List<ProfileResult>?
}
@JsonClass(generateAdapter = true)
data class SingleProfileResultRest(
override val name: String? = null,
override val depth: Int? = 0,
override val nestedResults: List<SingleProfileResultRest>? = null,
val startTime: Long? = 0L,
val execTime: Long? = 0L
) : ProfileResult
@JsonClass(generateAdapter = true)
data class ProfileReport(
@Json(name = "user")
val user: String? = null,
@Json(name = "device")
val device: String? = null,
@Json(name = "id")
val id: String? = null,
@Json(name = "rootProfileResults")
val rootProfileResult: SingleProfileResultRest? = null,
@Json(name = "tag")
val tag: String? = null
)

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2020 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.util.profiling
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import okhttp3.internal.tls.OkHostnameVerifier
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.internal.di.Unauthenticated
import org.matrix.android.sdk.internal.network.HttpHeaders
import org.matrix.android.sdk.internal.network.RetrofitFactory
@Module
internal abstract class PerfModule {
@Module
companion object {
@Provides
@JvmStatic
fun providesPrefServerAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
retrofitFactory: RetrofitFactory): PerServerAPI {
val client = okHttpClient.get().newBuilder()
.addInterceptor { chain ->
var request = chain.request()
val token = BuildConfig.PERF_TRACING_SERVER_TOKEN
val newRequestBuilder = request.newBuilder()
newRequestBuilder.header(HttpHeaders.Authorization, "Bearer $token")
request = newRequestBuilder.build()
chain.proceed(request)
}
.hostnameVerifier(OkHostnameVerifier)
.build()
return retrofitFactory.create(client, BuildConfig.PERF_TRACING_SERVER).create(PerServerAPI::class.java)
}
}
@Binds
abstract fun bindPublishTask(task: DefaultPublishPerfTask): PublishPerfTask
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2020 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.util.profiling
import android.os.Build
import com.nikitakozlov.pury.Plugin
import com.nikitakozlov.pury.profile.ProfilerId
import com.nikitakozlov.pury.result.model.ProfileResult
import com.nikitakozlov.pury.result.model.SingleProfileResult
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import javax.inject.Inject
internal class PerfServerPlugin @Inject constructor(
private val publishPerfTask: PublishPerfTask,
private val taskExecutor: TaskExecutor
) : Plugin {
override fun handleResult(result: ProfileResult?, profilerId: ProfilerId?) {
val report = ProfileReport(
user = BuildConfig.PERF_TRACING_SERVER_USER,
device = Build.DEVICE,
id = profilerId?.profilerName,
rootProfileResult = (result as? SingleProfileResult)?.let { ResultMapper.map(it) },
tag = "original"
)
publishPerfTask.configureWith(PublishPerfTask.Params(report))
.executeBy(taskExecutor)
}
object ResultMapper {
private const val MS_TO_NS = 1000000
fun map(singleProfileResult: SingleProfileResult): SingleProfileResultRest {
return SingleProfileResultRest(
name = singleProfileResult.stageName,
depth = singleProfileResult.depth,
execTime = singleProfileResult.execTime / MS_TO_NS,
startTime = singleProfileResult.startTime / MS_TO_NS,
nestedResults = singleProfileResult.nestedResults.filterIsInstance<SingleProfileResult>().map {
map(it)
}
)
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2020 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.util.profiling
import com.nikitakozlov.pury.Logger
import com.nikitakozlov.pury.Pury
import timber.log.Timber
object ProfilingConfiguration {
init {
Pury.setLogger(object : Logger {
override fun result(tag: String, message: String) {
Timber.tag(tag)
Timber.v(message)
}
override fun warning(tag: String, message: String) {
Timber.tag(tag)
Timber.w(message)
}
override fun error(tag: String, message: String) {
Timber.tag(tag)
Timber.e(message)
}
})
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020 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.util.profiling
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface PublishPerfTask : Task<PublishPerfTask.Params, Unit> {
data class Params(
val report: ProfileReport
)
}
internal class DefaultPublishPerfTask @Inject constructor(
private val perfAPI: PerServerAPI
) : PublishPerfTask {
override suspend fun execute(params: PublishPerfTask.Params) {
return executeRequest(null) {
apiCall = perfAPI.postReport(params.report)
}
}
}

View File

@@ -54,6 +54,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
@@ -653,12 +654,14 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Encrypt an event content according to the configuration of the room.
*
* @param eventId the identifier of the event
* @param eventContent the content of the event.
* @param eventType the type of the event.
* @param roomId the room identifier the event will be sent.
* @param callback the asynchronous callback
*/
override fun encryptEventContent(eventContent: Content,
override fun encryptEventContent(eventId: String,
eventContent: Content,
eventType: String,
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) {
@@ -667,7 +670,12 @@ internal class DefaultCryptoService @Inject constructor(
// Timber.v("## CRYPTO | encryptEventContent() : wait after e2e init")
// internalStart(false)
// }
val userIds = getRoomUserIds(roomId)
SendPerformanceProfiler.startStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_GET_USERS)
val userIds = getRoomUserIds(roomId)
SendPerformanceProfiler.stopStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_GET_USERS)
SendPerformanceProfiler.startStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_SET_ROOM_ENCRYPTION)
var alg = roomEncryptorsStore.get(roomId)
if (alg == null) {
val algorithm = getEncryptionAlgorithm(roomId)
@@ -677,12 +685,13 @@ internal class DefaultCryptoService @Inject constructor(
}
}
}
SendPerformanceProfiler.stopStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_SET_ROOM_ENCRYPTION)
val safeAlgorithm = alg
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
Timber.v("## CRYPTO | encryptEventContent() starts")
runCatching {
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
val content = safeAlgorithm.encryptEventContent(eventId, eventContent, eventType, userIds)
Timber.v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
@@ -803,17 +812,17 @@ internal class DefaultCryptoService @Inject constructor(
onRoomKeyEvent(event)
}
EventType.REQUEST_SECRET,
EventType.ROOM_KEY_REQUEST -> {
EventType.ROOM_KEY_REQUEST -> {
// save audit trail
cryptoStore.saveGossipingEvent(event)
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
incomingGossipingRequestManager.onGossipingRequestEvent(event)
}
EventType.SEND_SECRET -> {
EventType.SEND_SECRET -> {
cryptoStore.saveGossipingEvent(event)
onSecretSendReceived(event)
}
EventType.ROOM_KEY_WITHHELD -> {
EventType.ROOM_KEY_WITHHELD -> {
onKeyWithHeldReceived(event)
}
else -> {
@@ -892,7 +901,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
return when (secretName) {
MASTER_KEY_SSSS_NAME -> {
MASTER_KEY_SSSS_NAME -> {
crossSigningService.onSecretMSKGossip(secretValue)
true
}
@@ -1351,9 +1360,9 @@ internal class DefaultCryptoService @Inject constructor(
override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? {
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
}
/* ==========================================================================================
* For test only
* ========================================================================================== */
/* ==========================================================================================
* For test only
* ========================================================================================== */
@VisibleForTesting
val cryptoStoreForTesting = cryptoStore

View File

@@ -33,7 +33,7 @@ internal interface IMXEncrypting {
* @param userIds the room members the event will be sent to.
* @return the encrypted content
*/
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
suspend fun encryptEventContent(eventId: String, eventContent: Content, eventType: String, userIds: List<String>): Content
/**
* In Megolm, each recipient maintains a record of the ratchet value which allows

View File

@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
@@ -71,17 +72,20 @@ internal class MXMegolmEncryption(
private var sessionRotationPeriodMsgs: Int = 100
private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000
override suspend fun encryptEventContent(eventContent: Content,
override suspend fun encryptEventContent(eventId: String,
eventContent: Content,
eventType: String,
userIds: List<String>): Content {
val ts = System.currentTimeMillis()
Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
val devices = getDevicesInRoom(userIds)
Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
val outboundSession = ensureOutboundSession(devices.allowedDevices)
val outboundSession = ensureOutboundSession(eventId, devices.allowedDevices)
SendPerformanceProfiler.startStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_MEGOLM_ENCRYPT)
return encryptContent(outboundSession, eventType, eventContent)
.also {
SendPerformanceProfiler.stopStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_MEGOLM_ENCRYPT)
notifyWithheldForSession(devices.withHeldDevices, outboundSession)
}
}
@@ -128,7 +132,7 @@ internal class MXMegolmEncryption(
*
* @param devicesInRoom the devices list
*/
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
private suspend fun ensureOutboundSession(eventId: String, devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
Timber.v("## CRYPTO | ensureOutboundSession start")
var session = outboundSession
if (session == null
@@ -152,7 +156,9 @@ internal class MXMegolmEncryption(
}
}
}
SendPerformanceProfiler.startStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_MEGOLM_SHARE_KEYS)
shareKey(safeSession, shareMap)
SendPerformanceProfiler.stopStage(eventId, SendPerformanceProfiler.Stages.ENCRYPT_MEGOLM_SHARE_KEYS)
return safeSession
}
@@ -307,6 +313,7 @@ internal class MXMegolmEncryption(
// Get canonical Json from
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString)
val map = HashMap<String, Any>()

View File

@@ -38,7 +38,7 @@ internal class MXOlmEncryption(
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
: IMXEncrypting {
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
override suspend fun encryptEventContent(eventId: String, eventContent: Content, eventType: String, userIds: List<String>): Content {
// pick the list of recipients based on the membership list.
//
// TODO: there is a race condition here! What if a new user turns up

View File

@@ -54,7 +54,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
// try {
awaitCallback<MXEncryptEventContentResult> {
params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
params.crypto.encryptEventContent(localEvent.eventId, localMutableContent, localEvent.type, params.roomId, it)
}.let { result ->
val modifiedContent = HashMap(result.eventContent)
params.keepKeys?.forEach { toKeep ->

View File

@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.util.profiling.PerfModule
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.AuthModule
import org.matrix.android.sdk.internal.auth.SessionParamsStore
@@ -44,6 +45,7 @@ import java.io.File
NetworkModule::class,
AuthModule::class,
RawModule::class,
PerfModule::class,
NoOpTestModule::class
])
@MatrixScope

View File

@@ -21,11 +21,11 @@ import android.content.Context
import android.content.res.Resources
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.createBackgroundHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.createBackgroundHandler
import org.matrix.olm.OlmManager
import java.io.File
import java.util.concurrent.Executors

View File

@@ -22,11 +22,13 @@ import androidx.work.BackoffPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.Operation
import com.nikitakozlov.pury.annotations.StartProfiling
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.crypto.CryptoService
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.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isTextMessage
@@ -44,7 +46,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.CancelableBag
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider
@@ -81,14 +82,21 @@ internal class DefaultSendService @AssistedInject constructor(
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
override fun sendEvent(eventType: String, content: JsonDict?): Cancelable {
override fun sendEvent(eventType: String, content: Content?, onBuiltEvent: ((Event) -> Unit)?): Cancelable {
return localEchoEventFactory.createEvent(roomId, eventType, content)
.also { onBuiltEvent?.invoke(it) }
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
override fun sendTextMessage(
text: CharSequence,
msgType: String,
autoMarkdown: Boolean,
onBuiltEvent: ((Event) -> Unit)?
): Cancelable {
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
.also { onBuiltEvent?.invoke(it) }
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
@@ -106,8 +114,14 @@ internal class DefaultSendService @AssistedInject constructor(
}
}
override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable {
override fun sendFormattedTextMessage(
text: String,
formattedText: String,
msgType: String,
onBuiltEvent: ((Event) -> Unit)?
): Cancelable {
return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType)
.also { onBuiltEvent?.invoke(it) }
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
@@ -138,6 +152,7 @@ internal class DefaultSendService @AssistedInject constructor(
.let { timelineSendEventWorkCommon.postWork(roomId, it) }
}
@StartProfiling(profilerName = "Sending", stageName = "Send service", stageOrder = 0)
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable {
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)

View File

@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
@@ -65,7 +66,11 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
override suspend fun doSafeWork(params: Params): Result {
Timber.v("## SendEvent: Start Encrypt work for event ${params.eventId}")
SendPerformanceProfiler.startStage(params.eventId, SendPerformanceProfiler.Stages.GET_UP_TO_DATE_ECHO)
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
SendPerformanceProfiler.stopStage(params.eventId, SendPerformanceProfiler.Stages.GET_UP_TO_DATE_ECHO)
SendPerformanceProfiler.startStage(params.eventId, SendPerformanceProfiler.Stages.ENCRYPT_WORKER)
if (localEvent?.eventId == null) {
return Result.success()
}
@@ -86,7 +91,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
var result: MXEncryptEventContentResult? = null
try {
result = awaitCallback {
crypto.encryptEventContent(localMutableContent, localEvent.type, localEvent.roomId!!, it)
crypto.encryptEventContent(localEvent.eventId, localMutableContent, localEvent.type, localEvent.roomId!!, it)
}
} catch (throwable: Throwable) {
error = throwable
@@ -122,7 +127,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
localEcho.setDecryptionResult(it)
}
}
SendPerformanceProfiler.stopStage(params.eventId, SendPerformanceProfiler.Stages.ENCRYPT_WORKER)
val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, eventId = params.eventId)
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
} else {

View File

@@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.isReply
@@ -325,6 +326,7 @@ internal class LocalEchoEventFactory @Inject constructor(
fun createEvent(roomId: String, type: String, content: Content?): Event {
val localId = LocalEcho.createLocalEchoId()
SendPerformanceProfiler.startProfiling(localId)
return Event(
roomId = roomId,
originServerTs = dummyOriginServerTs(),
@@ -433,10 +435,10 @@ internal class LocalEchoEventFactory @Inject constructor(
TextContent(content.body, formattedText)
}
}
MessageType.MSGTYPE_FILE -> return TextContent("sent a file.")
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
MessageType.MSGTYPE_FILE -> return TextContent("sent a file.")
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
else -> return TextContent(content?.body ?: "")
}
}

View File

@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.database.RealmSessionProvider
@@ -62,6 +63,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
if (event.eventId == null) {
throw IllegalStateException("You should have set an eventId for your event")
}
// SendPerformanceProfiler.startStage(event.eventId, SendPerformanceProfiler.Stages.LOCAL_ECHO)
val timelineEventEntity = realmSessionProvider.withRealm { realm ->
val eventEntity = event.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
val roomMemberHelper = RoomMemberHelper(realm, roomId)
@@ -86,6 +88,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@asyncTransaction
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
roomSummaryUpdater.updateSendingInformation(realm, roomId)
//SendPerformanceProfiler.stopStage(event.eventId, SendPerformanceProfiler.Stages.LOCAL_ECHO)
}
}

View File

@@ -37,6 +37,7 @@ internal class RoomEventSender @Inject constructor(
@SessionId private val sessionId: String,
private val cryptoService: CryptoService
) {
fun sendEvent(event: Event): Cancelable {
// Encrypted room handling
return if (cryptoService.isRoomEncrypted(event.roomId ?: "")

View File

@@ -24,6 +24,7 @@ import io.realm.RealmConfiguration
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.executeRequest
@@ -62,6 +63,7 @@ internal class SendEventWorker(context: Context,
}
override suspend fun doSafeWork(params: Params): Result {
SendPerformanceProfiler.startStage(params.eventId, SendPerformanceProfiler.Stages.SEND_WORKER)
val event = localEchoRepository.getUpToDateEcho(params.eventId)
if (event?.eventId == null || event.roomId == null) {
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
@@ -87,6 +89,7 @@ internal class SendEventWorker(context: Context,
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
return try {
sendEvent(event.eventId, event.roomId, event.type, event.content)
SendPerformanceProfiler.stopStage(event.eventId, SendPerformanceProfiler.Stages.SEND_WORKER)
Result.success()
} catch (exception: Throwable) {
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
@@ -106,9 +109,11 @@ internal class SendEventWorker(context: Context,
private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
localEchoRepository.updateSendState(eventId, SendState.SENDING)
SendPerformanceProfiler.startStage(eventId, SendPerformanceProfiler.Stages.SEND_REQUEST)
executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.send(eventId, roomId, type, content)
}
SendPerformanceProfiler.stopStage(eventId, SendPerformanceProfiler.Stages.SEND_REQUEST)
localEchoRepository.updateSendState(eventId, SendState.SENT)
}
}

View File

@@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
import io.realm.Realm
import io.realm.kotlin.createObject
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import timber.log.Timber
import javax.inject.Inject
@@ -333,6 +334,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
event.unsignedData?.transactionId?.also {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
if (sendingEventEntity != null) {
SendPerformanceProfiler.profileBlock(it, SendPerformanceProfiler.Stages.RECEIVED_IN_SYNC)
Timber.v("Remove local echo for tx:$it")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
if (event.isEncrypted() && event.content?.get("algorithm") as? String == MXCRYPTO_ALGORITHM_MEGOLM) {

View File

@@ -32,8 +32,6 @@
import static
### Rubbish from merge. Please delete those lines (sometimes in comment)
<<<<<<<
>>>>>>>
### carry return before "}". Please remove empty lines.
\n\s*\n\s*\}
@@ -164,7 +162,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt
enum class===80
enum class===81
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3

View File

@@ -334,6 +334,9 @@ dependencies {
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
debugImplementation 'com.nikitakozlov.pury:pury:1.1.0'
releaseImplementation 'com.nikitakozlov.pury:pury-no-op:1.1.0'
// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

View File

@@ -94,6 +94,7 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import org.matrix.android.sdk.api.session.room.send.SendPerformanceProfiler
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
@@ -551,7 +552,9 @@ class RoomDetailViewModel @AssistedInject constructor(
when (val slashCommandResult = CommandParser.parseSplashCommand(action.text)) {
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) {
SendPerformanceProfiler.startProfiling(it.eventId!!)
}
_viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft()
}
@@ -890,6 +893,9 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) {
viewModelScope.launch(Dispatchers.Default) {
if (action.event.root.sendState.isSent()) { // ignore pending/local events
action.event.root.unsignedData?.transactionId?.also {
SendPerformanceProfiler.stopProfiling(it)
}
visibleEventsObservable.accept(action)
}
// We need to update this with the related m.replace also (to move read receipt)

View File

@@ -25,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.VisibilityState
import im.vector.app.BuildConfig
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.LoadingItem_
@@ -176,6 +177,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
}
init {
isDebugLoggingEnabled = BuildConfig.DEBUG
addInterceptor(this)
requestModelBuild()
}