Add receipt processing benchmark tests.

This commit is contained in:
Cody Henthorne
2026-02-25 08:59:05 -05:00
parent b6dd4a3579
commit c06944da13
13 changed files with 375 additions and 75 deletions

View File

@@ -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",

View File

@@ -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)
}
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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()
}
}
}

View File

@@ -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()
}
}

View File

@@ -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
)
}

View File

@@ -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

View File

@@ -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)
)
}

View File

@@ -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")
}

View File

@@ -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()
}
}
}

View File

@@ -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 = {