mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 00:17:41 +01:00
Add receipt processing benchmark tests.
This commit is contained in:
@@ -13,8 +13,13 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.signal.benchmark.setup.Generator
|
||||
import org.signal.benchmark.setup.Harness
|
||||
import org.signal.benchmark.setup.OtherClient
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.TestDbUtils
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.websocket.BenchmarkWebSocketConnection
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
|
||||
@@ -45,6 +50,8 @@ class BenchmarkCommandReceiver : BroadcastReceiver() {
|
||||
when (command) {
|
||||
"individual-send" -> handlePrepareIndividualSend()
|
||||
"group-send" -> handlePrepareGroupSend()
|
||||
"group-delivery-receipt" -> handlePrepareGroupReceipts { client, timestamps -> client.generateInboundDeliveryReceipts(timestamps) }
|
||||
"group-read-receipt" -> handlePrepareGroupReceipts { client, timestamps -> client.generateInboundReadReceipts(timestamps) }
|
||||
"release-messages" -> {
|
||||
BenchmarkWebSocketConnection.authInstance.startWholeBatchTrace = true
|
||||
BenchmarkWebSocketConnection.authInstance.releaseMessages()
|
||||
@@ -113,6 +120,62 @@ class BenchmarkCommandReceiver : BroadcastReceiver() {
|
||||
BenchmarkWebSocketConnection.authInstance.addQueueEmptyMessage()
|
||||
}
|
||||
|
||||
private fun handlePrepareGroupReceipts(generateReceipts: (OtherClient, List<Long>) -> List<Envelope>) {
|
||||
val clients = Harness.otherClients.take(5)
|
||||
|
||||
establishGroupSessions(clients)
|
||||
|
||||
val timestamps = getOutgoingGroupMessageTimestamps()
|
||||
Log.i(TAG, "Found ${timestamps.size} outgoing message timestamps for receipts")
|
||||
|
||||
val allClientEnvelopes = clients.map { client ->
|
||||
generateReceipts(client, timestamps).map { it.toWebSocketPayload() }
|
||||
}
|
||||
|
||||
BenchmarkWebSocketConnection.authInstance.addPendingMessages(interleave(allClientEnvelopes))
|
||||
BenchmarkWebSocketConnection.authInstance.addQueueEmptyMessage()
|
||||
}
|
||||
|
||||
private fun establishGroupSessions(clients: List<OtherClient>) {
|
||||
val encryptedEnvelopes = clients.map { it.encrypt(Generator.encryptedTextMessage(System.currentTimeMillis(), groupMasterKey = Harness.groupMasterKey)) }
|
||||
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
BenchmarkWebSocketConnection.authInstance.run {
|
||||
Log.i(TAG, "Sending initial group messages from clients to establish sessions.")
|
||||
addPendingMessages(encryptedEnvelopes.map { it.toWebSocketPayload() })
|
||||
releaseMessages()
|
||||
ThreadUtil.sleep(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOutgoingGroupMessageTimestamps(): List<Long> {
|
||||
val groupId = GroupId.v2(Harness.groupMasterKey)
|
||||
val groupRecipient = Recipient.externalGroupExact(groupId)
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
val selfId = Recipient.self().id.toLong()
|
||||
return TestDbUtils.getOutgoingMessageTimestamps(threadId, selfId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaves lists so that items from different lists alternate:
|
||||
* [[a1, a2], [b1, b2], [c1, c2]] -> [a1, b1, c1, a2, b2, c2]
|
||||
*/
|
||||
private fun <T> interleave(lists: List<List<T>>): List<T> {
|
||||
val result = mutableListOf<T>()
|
||||
val maxSize = lists.maxOf { it.size }
|
||||
for (i in 0 until maxSize) {
|
||||
for (list in lists) {
|
||||
if (i < list.size) {
|
||||
result += list[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun Envelope.toWebSocketPayload(): WebSocketRequestMessage {
|
||||
return WebSocketRequestMessage(
|
||||
verb = "PUT",
|
||||
|
||||
@@ -6,7 +6,10 @@ import org.signal.benchmark.setup.TestMessages
|
||||
import org.signal.benchmark.setup.TestUsers
|
||||
import org.thoughtcrime.securesms.BaseActivity
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.TestDbUtils
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class BenchmarkSetupActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -17,6 +20,8 @@ class BenchmarkSetupActivity : BaseActivity() {
|
||||
"conversation-open" -> setupConversationOpen()
|
||||
"message-send" -> setupMessageSend()
|
||||
"group-message-send" -> setupGroupMessageSend()
|
||||
"group-delivery-receipt" -> setupGroupReceipt(includeMsl = true)
|
||||
"group-read-receipt" -> setupGroupReceipt(enableReadReceipts = true)
|
||||
}
|
||||
|
||||
val textView: TextView = TextView(this).apply {
|
||||
@@ -68,4 +73,40 @@ class BenchmarkSetupActivity : BaseActivity() {
|
||||
TestUsers.setupSelf()
|
||||
TestUsers.setupGroup()
|
||||
}
|
||||
|
||||
private fun setupGroupReceipt(includeMsl: Boolean = false, enableReadReceipts: Boolean = false) {
|
||||
TestUsers.setupSelf()
|
||||
val groupId = TestUsers.setupGroup()
|
||||
|
||||
val groupRecipient = Recipient.externalGroupExact(groupId)
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
|
||||
val messageIds = mutableListOf<Long>()
|
||||
val timestamps = mutableListOf<Long>()
|
||||
val baseTimestamp = 2_000_000L
|
||||
|
||||
for (i in 0 until 100) {
|
||||
val timestamp = baseTimestamp + i
|
||||
val message = OutgoingMessage(
|
||||
recipient = groupRecipient,
|
||||
body = "Outgoing message $i",
|
||||
timestamp = timestamp,
|
||||
isSecure = true
|
||||
)
|
||||
val insert = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
|
||||
SignalDatabase.messages.markAsSent(insert.messageId, true)
|
||||
messageIds += insert.messageId
|
||||
timestamps += timestamp
|
||||
}
|
||||
|
||||
if (includeMsl) {
|
||||
val selfId = Recipient.self().id
|
||||
val memberRecipientIds = SignalDatabase.groups.getGroup(groupId).get().members.filter { it != selfId }
|
||||
TestDbUtils.insertMessageSendLogEntries(messageIds, timestamps, memberRecipientIds)
|
||||
}
|
||||
|
||||
if (enableReadReceipts) {
|
||||
TextSecurePreferences.setReadReceiptsEnabled(this, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
|
||||
import org.whispersystems.signalservice.internal.push.ReceiptMessage
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -45,6 +46,26 @@ object Generator {
|
||||
return EnvelopeContent.encrypted(content.build(), ContentHint.RESENDABLE, Optional.empty())
|
||||
}
|
||||
|
||||
fun encryptedDeliveryReceipt(now: Long, timestamps: List<Long>): EnvelopeContent {
|
||||
return encryptedReceipt(ReceiptMessage.Type.DELIVERY, timestamps)
|
||||
}
|
||||
|
||||
fun encryptedReadReceipt(now: Long, timestamps: List<Long>): EnvelopeContent {
|
||||
return encryptedReceipt(ReceiptMessage.Type.READ, timestamps)
|
||||
}
|
||||
|
||||
private fun encryptedReceipt(type: ReceiptMessage.Type, timestamps: List<Long>): EnvelopeContent {
|
||||
val content = Content.Builder().apply {
|
||||
receiptMessage(
|
||||
ReceiptMessage.Builder().buildWith {
|
||||
this.type = type
|
||||
timestamp = timestamps
|
||||
}
|
||||
)
|
||||
}
|
||||
return EnvelopeContent.encrypted(content.build(), ContentHint.IMPLICIT, Optional.empty())
|
||||
}
|
||||
|
||||
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
|
||||
val serverGuid = UUID.randomUUID()
|
||||
return Envelope.Builder()
|
||||
|
||||
@@ -62,6 +62,10 @@ class OtherClient(val serviceId: ServiceId, val e164: String, val identityKeyPai
|
||||
|
||||
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
|
||||
fun encrypt(envelopeContent: EnvelopeContent): Envelope {
|
||||
return encrypt(envelopeContent, envelopeContent.content.get().dataMessage!!.timestamp!!)
|
||||
}
|
||||
|
||||
fun encrypt(envelopeContent: EnvelopeContent, timestamp: Long): Envelope {
|
||||
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
|
||||
|
||||
if (!aciStore.containsSession(getAliceProtocolAddress())) {
|
||||
@@ -70,7 +74,7 @@ class OtherClient(val serviceId: ServiceId, val e164: String, val identityKeyPai
|
||||
}
|
||||
|
||||
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent)
|
||||
.toEnvelope(envelopeContent.content.get().dataMessage!!.timestamp!!, getAliceServiceId())
|
||||
.toEnvelope(timestamp, getAliceServiceId())
|
||||
}
|
||||
|
||||
fun generateInboundEnvelopes(count: Int): List<Envelope> {
|
||||
@@ -84,6 +88,24 @@ class OtherClient(val serviceId: ServiceId, val e164: String, val identityKeyPai
|
||||
return envelopes
|
||||
}
|
||||
|
||||
fun generateInboundDeliveryReceipts(messageTimestamps: List<Long>): List<Envelope> {
|
||||
return generateInboundReceipts(messageTimestamps, Generator::encryptedDeliveryReceipt)
|
||||
}
|
||||
|
||||
fun generateInboundReadReceipts(messageTimestamps: List<Long>): List<Envelope> {
|
||||
return generateInboundReceipts(messageTimestamps, Generator::encryptedReadReceipt)
|
||||
}
|
||||
|
||||
private fun generateInboundReceipts(messageTimestamps: List<Long>, receiptFactory: (Long, List<Long>) -> EnvelopeContent): List<Envelope> {
|
||||
val envelopes = ArrayList<Envelope>(messageTimestamps.size)
|
||||
var now = System.currentTimeMillis()
|
||||
for (messageTimestamp in messageTimestamps) {
|
||||
envelopes += encrypt(receiptFactory(now, listOf(messageTimestamp)), now)
|
||||
now += 3
|
||||
}
|
||||
return envelopes
|
||||
}
|
||||
|
||||
fun generateInboundGroupEnvelopes(count: Int, groupMasterKey: GroupMasterKey): List<Envelope> {
|
||||
val envelopes = ArrayList<Envelope>(count)
|
||||
var now = System.currentTimeMillis()
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.keyvalue.CertificateType
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.Skipped
|
||||
@@ -170,7 +171,7 @@ object TestUsers {
|
||||
return others
|
||||
}
|
||||
|
||||
fun setupGroup() {
|
||||
fun setupGroup(): GroupId.V2 {
|
||||
val members = setupTestClients(5)
|
||||
val self = Recipient.self()
|
||||
|
||||
@@ -202,6 +203,8 @@ object TestUsers {
|
||||
)
|
||||
|
||||
SignalDatabase.recipients.setProfileSharing(Recipient.externalGroupExact(groupId!!).id, true)
|
||||
|
||||
return groupId
|
||||
}
|
||||
|
||||
private fun member(aci: ACI, role: Member.Role = Member.Role.DEFAULT, joinedAt: Int = 0, labelEmoji: String = "", labelString: String = ""): DecryptedMember {
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import org.signal.core.util.SqlUtil.buildArgs
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
|
||||
object TestDbUtils {
|
||||
|
||||
@@ -11,4 +13,58 @@ object TestDbUtils {
|
||||
contentValues.put(MessageTable.DATE_RECEIVED, timestamp)
|
||||
val rowsUpdated = database.update(MessageTable.TABLE_NAME, contentValues, DatabaseTable.ID_WHERE, buildArgs(messageId))
|
||||
}
|
||||
|
||||
fun getOutgoingMessageTimestamps(threadId: Long, selfRecipientId: Long): List<Long> {
|
||||
val timestamps = mutableListOf<Long>()
|
||||
SignalDatabase.messages.databaseHelper.signalReadableDatabase.query(
|
||||
MessageTable.TABLE_NAME,
|
||||
arrayOf(MessageTable.DATE_SENT),
|
||||
"${MessageTable.THREAD_ID} = ? AND ${MessageTable.FROM_RECIPIENT_ID} = ?",
|
||||
arrayOf(threadId.toString(), selfRecipientId.toString()),
|
||||
null,
|
||||
null,
|
||||
"${MessageTable.DATE_SENT} ASC"
|
||||
).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
timestamps += cursor.getLong(0)
|
||||
}
|
||||
}
|
||||
return timestamps
|
||||
}
|
||||
|
||||
fun insertMessageSendLogEntries(messageIds: List<Long>, timestamps: List<Long>, recipientIds: List<RecipientId>) {
|
||||
val db = SignalDatabase.messages.databaseHelper.signalWritableDatabase
|
||||
val dummyContent = Content.Builder().build().encode()
|
||||
|
||||
db.beginTransaction()
|
||||
try {
|
||||
for (i in messageIds.indices) {
|
||||
val payloadValues = ContentValues().apply {
|
||||
put("date_sent", timestamps[i])
|
||||
put("content", dummyContent)
|
||||
put("content_hint", 0)
|
||||
put("urgent", 1)
|
||||
}
|
||||
val payloadId = db.insert("msl_payload", null, payloadValues)
|
||||
|
||||
val messageValues = ContentValues().apply {
|
||||
put("payload_id", payloadId)
|
||||
put("message_id", messageIds[i])
|
||||
}
|
||||
db.insert("msl_message", null, messageValues)
|
||||
|
||||
for (recipientId in recipientIds) {
|
||||
val recipientValues = ContentValues().apply {
|
||||
put("payload_id", payloadId)
|
||||
put("recipient_id", recipientId.toLong())
|
||||
put("device", 1)
|
||||
}
|
||||
db.insert("msl_recipient", null, recipientValues)
|
||||
}
|
||||
}
|
||||
db.setTransactionSuccessful()
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.groupMasterKey
|
||||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.hasGroupContext
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import java.util.Optional
|
||||
|
||||
@@ -65,8 +66,13 @@ abstract class BatchCache {
|
||||
SignalDatabase.threads.updateForMessageInsert(threadId, unarchive = true)
|
||||
}
|
||||
|
||||
protected fun flushMslDelete(recipientId: RecipientId, device: Int, timestamps: List<Long>) {
|
||||
SignalDatabase.messageLog.deleteEntriesForRecipient(timestamps, recipientId, device)
|
||||
}
|
||||
|
||||
abstract fun addJob(job: Job)
|
||||
abstract fun addIncomingMessageInsertThreadUpdate(threadId: Long)
|
||||
abstract fun addMslDelete(recipientId: RecipientId, device: Int, timestamps: List<Long>)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +89,10 @@ class OneTimeBatchCache : BatchCache() {
|
||||
override fun addIncomingMessageInsertThreadUpdate(threadId: Long) {
|
||||
flushIncomingMessageInsertThreadUpdate(threadId)
|
||||
}
|
||||
|
||||
override fun addMslDelete(recipientId: RecipientId, device: Int, timestamps: List<Long>) {
|
||||
flushMslDelete(recipientId, device, timestamps)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,6 +110,7 @@ class ReusedBatchCache : BatchCache() {
|
||||
|
||||
private val batchedJobs = ArrayList<Job>(BATCH_SIZE)
|
||||
private val threadUpdates = HashSet<Long>(BATCH_SIZE)
|
||||
private val mslDeletes = HashMap<Pair<RecipientId, Int>, MutableList<Long>>(BATCH_SIZE)
|
||||
|
||||
override fun addJob(job: Job) {
|
||||
batchedJobs += job
|
||||
@@ -109,6 +120,10 @@ class ReusedBatchCache : BatchCache() {
|
||||
threadUpdates += threadId
|
||||
}
|
||||
|
||||
override fun addMslDelete(recipientId: RecipientId, device: Int, timestamps: List<Long>) {
|
||||
mslDeletes.getOrPut(recipientId to device) { mutableListOf() } += timestamps
|
||||
}
|
||||
|
||||
override fun flushAndClear() {
|
||||
super.flushAndClear()
|
||||
|
||||
@@ -123,5 +138,12 @@ class ReusedBatchCache : BatchCache() {
|
||||
}
|
||||
}
|
||||
threadUpdates.clear()
|
||||
|
||||
if (mslDeletes.isNotEmpty()) {
|
||||
SignalDatabase.runInTransaction {
|
||||
mslDeletes.forEach { (key, timestamps) -> flushMslDelete(key.first, key.second, timestamps) }
|
||||
}
|
||||
}
|
||||
mslDeletes.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +496,8 @@ open class MessageContentProcessor(private val context: Context) {
|
||||
envelope,
|
||||
content,
|
||||
metadata,
|
||||
if (processingEarlyContent) null else EarlyMessageCacheEntry(envelope, content, metadata, serverDeliveredTimestamp)
|
||||
if (processingEarlyContent) null else EarlyMessageCacheEntry(envelope, content, metadata, serverDeliveredTimestamp),
|
||||
batchCache
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.messages.MessageContentProcessor.Companion.war
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.EarlyMessageCacheEntry
|
||||
import org.thoughtcrime.securesms.util.SignalTrace
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
@@ -23,11 +24,11 @@ object ReceiptMessageProcessor {
|
||||
|
||||
private const val VERBOSE = false
|
||||
|
||||
fun process(context: Context, senderRecipient: Recipient, envelope: Envelope, content: Content, metadata: EnvelopeMetadata, earlyMessageCacheEntry: EarlyMessageCacheEntry?) {
|
||||
fun process(context: Context, senderRecipient: Recipient, envelope: Envelope, content: Content, metadata: EnvelopeMetadata, earlyMessageCacheEntry: EarlyMessageCacheEntry?, batchCache: BatchCache) {
|
||||
val receiptMessage = content.receiptMessage!!
|
||||
|
||||
when (receiptMessage.type) {
|
||||
ReceiptMessage.Type.DELIVERY -> handleDeliveryReceipt(envelope, metadata, receiptMessage, senderRecipient.id)
|
||||
ReceiptMessage.Type.DELIVERY -> handleDeliveryReceipt(envelope, metadata, receiptMessage, senderRecipient.id, batchCache)
|
||||
ReceiptMessage.Type.READ -> handleReadReceipt(context, senderRecipient.id, envelope, metadata, receiptMessage, earlyMessageCacheEntry)
|
||||
ReceiptMessage.Type.VIEWED -> handleViewedReceipt(context, envelope, metadata, receiptMessage, senderRecipient.id, earlyMessageCacheEntry)
|
||||
else -> warn(envelope.timestamp!!, "Unknown recipient message type ${receiptMessage.type}")
|
||||
@@ -39,12 +40,15 @@ object ReceiptMessageProcessor {
|
||||
envelope: Envelope,
|
||||
metadata: EnvelopeMetadata,
|
||||
deliveryReceipt: ReceiptMessage,
|
||||
senderRecipientId: RecipientId
|
||||
senderRecipientId: RecipientId,
|
||||
batchCache: BatchCache
|
||||
) {
|
||||
log(envelope.timestamp!!, "Processing delivery receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Timestamps: ${deliveryReceipt.timestamp.joinToString(", ")}")
|
||||
val stopwatch: Stopwatch? = if (VERBOSE) Stopwatch("delivery-receipt", decimalPlaces = 2) else null
|
||||
|
||||
SignalTrace.beginSection("ReceiptMessageProcessor#incrementDeliveryReceiptCounts")
|
||||
val missingTargetTimestamps: Set<Long> = SignalDatabase.messages.incrementDeliveryReceiptCounts(deliveryReceipt.timestamp, senderRecipientId, envelope.timestamp!!, stopwatch)
|
||||
SignalTrace.endSection()
|
||||
|
||||
for (targetTimestamp in missingTargetTimestamps) {
|
||||
warn(envelope.timestamp!!, "[handleDeliveryReceipt] Could not find matching message! targetTimestamp: $targetTimestamp, receiptAuthor: $senderRecipientId")
|
||||
@@ -58,7 +62,7 @@ object ReceiptMessageProcessor {
|
||||
SignalDatabase.pendingPniSignatureMessages.acknowledgeReceipts(senderRecipientId, deliveryReceipt.timestamp, metadata.sourceDeviceId)
|
||||
stopwatch?.split("pni-signatures")
|
||||
|
||||
SignalDatabase.messageLog.deleteEntriesForRecipient(deliveryReceipt.timestamp, senderRecipientId, metadata.sourceDeviceId)
|
||||
batchCache.addMslDelete(senderRecipientId, metadata.sourceDeviceId, deliveryReceipt.timestamp)
|
||||
stopwatch?.split("msl")
|
||||
|
||||
stopwatch?.stop(TAG)
|
||||
@@ -80,7 +84,9 @@ object ReceiptMessageProcessor {
|
||||
|
||||
log(envelope.timestamp!!, "Processing read receipts. Sender: $senderRecipientId, Device: ${metadata.sourceDeviceId}, Timestamps: ${readReceipt.timestamp.joinToString(", ")}")
|
||||
|
||||
SignalTrace.beginSection("ReceiptMessageProcessor#incrementReadReceiptCounts")
|
||||
val missingTargetTimestamps: Set<Long> = SignalDatabase.messages.incrementReadReceiptCounts(readReceipt.timestamp, senderRecipientId, envelope.timestamp!!)
|
||||
SignalTrace.endSection()
|
||||
|
||||
if (missingTargetTimestamps.isNotEmpty()) {
|
||||
val selfId = Recipient.self().id
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.benchmark
|
||||
|
||||
import androidx.benchmark.macro.ExperimentalMetricApi
|
||||
import androidx.benchmark.macro.TraceSectionMetric
|
||||
import androidx.benchmark.macro.TraceSectionMetric.Mode
|
||||
|
||||
@OptIn(ExperimentalMetricApi::class)
|
||||
object BenchmarkMetrics {
|
||||
|
||||
val incomingMessageObserver: List<TraceSectionMetric>
|
||||
get() = listOf(
|
||||
TraceSectionMetric("IncomingMessageObserver#decryptMessage", Mode.Average),
|
||||
TraceSectionMetric("IncomingMessageObserver#perMessageTransaction", Mode.Average),
|
||||
TraceSectionMetric("IncomingMessageObserver#processMessage", Mode.Average),
|
||||
TraceSectionMetric("IncomingMessageObserver#totalProcessing", Mode.Sum)
|
||||
)
|
||||
|
||||
val dataMessageProcessor: List<TraceSectionMetric>
|
||||
get() = listOf(
|
||||
TraceSectionMetric("DataMessageProcessor#gv2PreProcessing", Mode.Average),
|
||||
TraceSectionMetric("DataMessageProcessor#messageInsert", Mode.Average),
|
||||
TraceSectionMetric("DataMessageProcessor#postProcess", Mode.Average)
|
||||
)
|
||||
|
||||
val messageContentProcessor: List<TraceSectionMetric>
|
||||
get() = listOf(
|
||||
TraceSectionMetric("MessageContentProcessor#handleMessage", Mode.Average)
|
||||
)
|
||||
|
||||
val deliveryReceipt: List<TraceSectionMetric>
|
||||
get() = listOf(
|
||||
TraceSectionMetric("ReceiptMessageProcessor#incrementDeliveryReceiptCounts", Mode.Average)
|
||||
)
|
||||
|
||||
val readReceipt: List<TraceSectionMetric>
|
||||
get() = listOf(
|
||||
TraceSectionMetric("ReceiptMessageProcessor#incrementReadReceiptCounts", Mode.Average)
|
||||
)
|
||||
}
|
||||
@@ -22,6 +22,14 @@ object BenchmarkSetup {
|
||||
device.benchmarkCommandBroadcast("group-send")
|
||||
}
|
||||
|
||||
fun setupGroupDeliveryReceipt(device: UiDevice) {
|
||||
device.benchmarkCommandBroadcast("group-delivery-receipt")
|
||||
}
|
||||
|
||||
fun setupGroupReadReceipt(device: UiDevice) {
|
||||
device.benchmarkCommandBroadcast("group-read-receipt")
|
||||
}
|
||||
|
||||
fun releaseMessages(device: UiDevice) {
|
||||
device.benchmarkCommandBroadcast("release-messages")
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ package org.thoughtcrime.benchmark
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.benchmark.macro.CompilationMode
|
||||
import androidx.benchmark.macro.ExperimentalMetricApi
|
||||
import androidx.benchmark.macro.TraceSectionMetric
|
||||
import androidx.benchmark.macro.TraceSectionMetric.Mode
|
||||
import androidx.benchmark.macro.MacrobenchmarkScope
|
||||
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -30,62 +30,22 @@ class GroupMessageProcessingBenchmarks {
|
||||
|
||||
@Test
|
||||
fun groupMessageReceiveOnConversationList() {
|
||||
run(withConversationOpen = false)
|
||||
runGroupMessageReceive(withConversationOpen = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun individualMessageReceiveOnConversation() {
|
||||
run(withConversationOpen = true)
|
||||
fun groupMessageReceiveOnConversation() {
|
||||
runGroupMessageReceive(withConversationOpen = true)
|
||||
}
|
||||
|
||||
private fun run(withConversationOpen: Boolean) {
|
||||
private fun runGroupMessageReceive(withConversationOpen: Boolean) {
|
||||
benchmarkRule.measureRepeated(
|
||||
packageName = "org.thoughtcrime.securesms.benchmark",
|
||||
metrics = listOf(
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#decryptMessage",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#perMessageTransaction",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "DataMessageProcessor#gv2PreProcessing",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "DataMessageProcessor#messageInsert",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "DataMessageProcessor#postProcess",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#processMessage",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#totalProcessing",
|
||||
mode = Mode.Sum
|
||||
)
|
||||
),
|
||||
metrics = BenchmarkMetrics.incomingMessageObserver + BenchmarkMetrics.messageContentProcessor + BenchmarkMetrics.dataMessageProcessor,
|
||||
iterations = 5,
|
||||
compilationMode = CompilationMode.Partial(),
|
||||
setupBlock = {
|
||||
BenchmarkSetup.setup("group-message-send", device)
|
||||
|
||||
killProcess()
|
||||
startActivityAndWait()
|
||||
device.waitForIdle()
|
||||
|
||||
BenchmarkSetup.setupGroupSend(device)
|
||||
|
||||
val uiObject = device.wait(Until.findObject(By.textContains("Title")), 5_000)
|
||||
if (withConversationOpen) {
|
||||
uiObject.click()
|
||||
}
|
||||
setupGroup("group-message-send", BenchmarkSetup::setupGroupSend, withConversationOpen)
|
||||
}
|
||||
) {
|
||||
|
||||
@@ -94,4 +54,76 @@ class GroupMessageProcessingBenchmarks {
|
||||
device.wait(Until.hasObject(By.textContains("505")),10_000L)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun groupDeliveryReceiptOnConversationList() {
|
||||
runGroupDeliveryReceipt(withConversationOpen = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun groupDeliveryReceiptOnConversation() {
|
||||
runGroupDeliveryReceipt(withConversationOpen = true)
|
||||
}
|
||||
|
||||
private fun runGroupDeliveryReceipt(withConversationOpen: Boolean) {
|
||||
benchmarkRule.measureRepeated(
|
||||
packageName = "org.thoughtcrime.securesms.benchmark",
|
||||
metrics = BenchmarkMetrics.incomingMessageObserver + BenchmarkMetrics.messageContentProcessor + BenchmarkMetrics.deliveryReceipt,
|
||||
iterations = 5,
|
||||
compilationMode = CompilationMode.Partial(),
|
||||
setupBlock = {
|
||||
setupGroup("group-delivery-receipt", BenchmarkSetup::setupGroupDeliveryReceipt, withConversationOpen)
|
||||
}
|
||||
) {
|
||||
BenchmarkSetup.releaseMessages(device)
|
||||
|
||||
Thread.sleep(10_000)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun groupReadReceiptOnConversationList() {
|
||||
runGroupReadReceipt(withConversationOpen = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun groupReadReceiptOnConversation() {
|
||||
runGroupReadReceipt(withConversationOpen = true)
|
||||
}
|
||||
|
||||
private fun runGroupReadReceipt(withConversationOpen: Boolean) {
|
||||
benchmarkRule.measureRepeated(
|
||||
packageName = "org.thoughtcrime.securesms.benchmark",
|
||||
metrics = BenchmarkMetrics.incomingMessageObserver + BenchmarkMetrics.messageContentProcessor + BenchmarkMetrics.readReceipt,
|
||||
iterations = 5,
|
||||
compilationMode = CompilationMode.Partial(),
|
||||
setupBlock = {
|
||||
setupGroup("group-read-receipt", BenchmarkSetup::setupGroupReadReceipt, withConversationOpen)
|
||||
}
|
||||
) {
|
||||
BenchmarkSetup.releaseMessages(device)
|
||||
|
||||
Thread.sleep(10_000)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MacrobenchmarkScope.setupGroup(
|
||||
setupType: String,
|
||||
prepareCommand: (UiDevice) -> Unit,
|
||||
withConversationOpen: Boolean
|
||||
) {
|
||||
BenchmarkSetup.setup(setupType, device)
|
||||
|
||||
killProcess()
|
||||
startActivityAndWait()
|
||||
device.waitForIdle()
|
||||
|
||||
prepareCommand(device)
|
||||
|
||||
device.wait(Until.findObject(By.textContains("Title")), 5_000)
|
||||
if (withConversationOpen) {
|
||||
device.waitForIdle()
|
||||
device.findObject(By.textContains("Title")).click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ package org.thoughtcrime.benchmark
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.benchmark.macro.CompilationMode
|
||||
import androidx.benchmark.macro.ExperimentalMetricApi
|
||||
import androidx.benchmark.macro.TraceSectionMetric
|
||||
import androidx.benchmark.macro.TraceSectionMetric.Mode
|
||||
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
@@ -41,24 +39,7 @@ class MessageProcessingBenchmarks {
|
||||
private fun run(withConversationOpen: Boolean) {
|
||||
benchmarkRule.measureRepeated(
|
||||
packageName = "org.thoughtcrime.securesms.benchmark",
|
||||
metrics = listOf(
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#decryptMessage",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "MessageContentProcessor#handleMessage",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#processMessage",
|
||||
mode = Mode.Average
|
||||
),
|
||||
TraceSectionMetric(
|
||||
sectionName = "IncomingMessageObserver#totalProcessing",
|
||||
mode = Mode.Sum
|
||||
)
|
||||
),
|
||||
metrics = BenchmarkMetrics.incomingMessageObserver + BenchmarkMetrics.messageContentProcessor,
|
||||
iterations = 5,
|
||||
compilationMode = CompilationMode.Partial(),
|
||||
setupBlock = {
|
||||
|
||||
Reference in New Issue
Block a user