From 97b349b0decdc44067fb97feaa07aa65e2b502d4 Mon Sep 17 00:00:00 2001 From: Clark Date: Wed, 15 Mar 2023 17:13:02 -0400 Subject: [PATCH] Add benchmark for conversation open. --- .../benchmark/BenchmarkSetupActivity.kt | 234 ++++-------------- .../signal/benchmark/setup/TestMessages.kt | 189 ++++++++++++++ .../org/signal/benchmark/setup/TestUsers.kt | 103 ++++++++ .../securesms/database/TestDbUtils.kt | 14 ++ .../conversation/ConversationFragment.java | 3 + .../benchmark/BaselineProfileGenerator.kt | 30 ++- .../thoughtcrime/benchmark/BenchmarkSetup.kt | 12 + .../benchmark/ConversationBenchmarks.kt | 42 ++++ .../benchmark/StartupBenchmarks.kt | 21 +- 9 files changed, 429 insertions(+), 219 deletions(-) create mode 100644 app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt create mode 100644 app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt create mode 100644 app/src/benchmark/java/org/thoughtcrime/securesms/database/TestDbUtils.kt create mode 100644 benchmark/src/main/java/org/thoughtcrime/benchmark/BenchmarkSetup.kt create mode 100644 benchmark/src/main/java/org/thoughtcrime/benchmark/ConversationBenchmarks.kt diff --git a/app/src/benchmark/java/org/signal/benchmark/BenchmarkSetupActivity.kt b/app/src/benchmark/java/org/signal/benchmark/BenchmarkSetupActivity.kt index 3152ce2f5d..aeb5cb98de 100644 --- a/app/src/benchmark/java/org/signal/benchmark/BenchmarkSetupActivity.kt +++ b/app/src/benchmark/java/org/signal/benchmark/BenchmarkSetupActivity.kt @@ -1,214 +1,66 @@ package org.signal.benchmark -import android.annotation.SuppressLint -import android.content.SharedPreferences import android.os.Bundle -import android.preference.PreferenceManager -import org.signal.core.util.ThreadUtil -import org.signal.libsignal.protocol.IdentityKeyPair -import org.signal.libsignal.protocol.SignalProtocolAddress +import android.widget.TextView +import org.signal.benchmark.setup.TestMessages +import org.signal.benchmark.setup.TestUsers import org.thoughtcrime.securesms.BaseActivity -import org.thoughtcrime.securesms.attachments.PointerAttachment -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil -import org.thoughtcrime.securesms.crypto.MasterSecretUtil -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.database.SignalDatabase -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies -import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.mms.IncomingMediaMessage -import org.thoughtcrime.securesms.mms.OutgoingMessage -import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor -import org.thoughtcrime.securesms.profiles.ProfileName -import org.thoughtcrime.securesms.push.AccountManagerFactory +import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord +import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.registration.RegistrationData -import org.thoughtcrime.securesms.registration.RegistrationRepository -import org.thoughtcrime.securesms.registration.RegistrationUtil -import org.thoughtcrime.securesms.registration.VerifyResponse -import org.thoughtcrime.securesms.releasechannel.ReleaseChannel -import org.thoughtcrime.securesms.util.Util -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.SignalServiceAddress -import org.whispersystems.signalservice.internal.ServiceResponse -import org.whispersystems.signalservice.internal.ServiceResponseProcessor -import org.whispersystems.signalservice.internal.push.VerifyAccountResponse -import java.util.* class BenchmarkSetupActivity : BaseActivity() { - - private val othersCount: Int = 50 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setupSelf() - setupOthers() - } - - @SuppressLint("VisibleForTests") - private fun setupSelf(): Recipient { - DeviceTransferBlockingInterceptor.getInstance().blockNetwork() - - PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit() - val masterSecret = MasterSecretUtil.generateMasterSecret(application, MasterSecretUtil.UNENCRYPTED_PASSPHRASE) - MasterSecretUtil.generateAsymmetricMasterSecret(application, masterSecret) - val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0) - preferences.edit().putBoolean("passphrase_initialized", true).commit() - - val registrationRepository = RegistrationRepository(application) - val registrationData = RegistrationData( - code = "123123", - e164 = "+15555550101", - password = Util.getSecret(18), - registrationId = registrationRepository.registrationId, - profileKey = registrationRepository.getProfileKey("+15555550101"), - fcmToken = "fcm-token", - pniRegistrationId = registrationRepository.pniRegistrationId, - recoveryPassword = "asdfasdfasdfasdf" - ) - val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null) - AccountManagerFactory.setInstance(DummyAccountManagerFactory()) - val response: ServiceResponse = registrationRepository.registerAccount( - registrationData, - verifyResponse, - false - ).blockingGet() - ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow - - SignalStore.kbsValues().optOut() - RegistrationUtil.maybeMarkRegistrationComplete() - SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) - - return Recipient.self() - } - - private fun setupOthers(): Pair, List> { - val others = mutableListOf() - val othersKeys = mutableListOf() - - if (othersCount !in 0 until 1000) { - throw IllegalArgumentException("$othersCount must be between 0 and 1000") + when (intent.extras!!.getString("setup-type")) { + "cold-start" -> setupColdStart() + "conversation-open" -> setupConversationOpen() } - for (i in 0 until othersCount) { - val aci = ACI.from(UUID.randomUUID()) - val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i))) - SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i")) - SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew()) - SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true)) - SignalDatabase.recipients.setProfileSharing(recipientId, true) - SignalDatabase.recipients.markRegistered(recipientId, aci) - val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair() - ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey) + val textView: TextView = TextView(this).apply { + text = "done" + } + setContentView(textView) + } - val recipient: Recipient = Recipient.resolved(recipientId) + private fun setupColdStart() { + TestUsers.setupSelf() + TestUsers.setupTestRecipients(50).forEach { + val recipient: Recipient = Recipient.resolved(it) - insertMediaMessage(other = recipient, body = "Cool text message?!?!", attachmentCount = 0) - insertFailedMediaMessage(other = recipient, attachmentCount = 1) - insertFailedMediaMessage(other = recipient, attachmentCount = 2) - insertFailedMediaMessage(other = recipient, body = "Test", attachmentCount = 1) + TestMessages.insertIncomingTextMessage(other = recipient, body = "Cool text message?!?!") + TestMessages.insertIncomingImageMessage(other = recipient, attachmentCount = 1) + TestMessages.insertIncomingImageMessage(other = recipient, attachmentCount = 2, body = "Album") + TestMessages.insertIncomingImageMessage(other = recipient, body = "Test", attachmentCount = 1, failed = true) + + SignalDatabase.messages.setAllMessagesRead() SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true) - - others += recipientId - othersKeys += otherIdentity } - - SignalDatabase.messages.setAllMessagesRead() - - return others to othersKeys } - private fun insertMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) { - val attachments: List = (0 until attachmentCount).map { - attachment() + private fun setupConversationOpen() { + TestUsers.setupSelf() + TestUsers.setupTestRecipient().let { + val recipient: Recipient = Recipient.resolved(it) + val messagesToAdd = 1000 + val generator: TestMessages.TimestampGenerator = TestMessages.TimestampGenerator(System.currentTimeMillis() - (messagesToAdd * 2000L) - 60_000L) + + for (i in 0 until messagesToAdd) { + TestMessages.insertIncomingTextMessage(other = recipient, body = "Test message $i", timestamp = generator.nextTimestamp()) + TestMessages.insertOutgoingTextMessage(other = recipient, body = "Test message $i", timestamp = generator.nextTimestamp()) + } + + val voiceMessageId = TestMessages.insertIncomingVoiceMessage(other = recipient, timestamp = generator.nextTimestamp()) + val mmsRecord = SignalDatabase.messages.getMessageRecord(voiceMessageId) as MediaMmsMessageRecord + TestMessages.insertOutgoingImageMessage(other = recipient, body = "test", 2, generator.nextTimestamp()) + TestMessages.insertIncomingTextMessage(other = recipient, "reply to the test message", generator.nextTimestamp()) + TestMessages.insertIncomingQuoteTextMessage(other = recipient, quote = QuoteModel(mmsRecord.timestamp, recipient.id, "Fake voice message text", false, mmsRecord.slideDeck.asAttachments(), null, QuoteModel.Type.NORMAL, null), body = "Here is a cool quote", timestamp = generator.nextTimestamp()) + TestMessages.insertOutgoingTextMessage(other = recipient, body = "longaweorijoaijwerijoiajwer", timestamp = generator.nextTimestamp()) + + SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true) } - - val message = IncomingMediaMessage( - from = other.id, - body = body, - sentTimeMillis = System.currentTimeMillis(), - serverTimeMillis = System.currentTimeMillis(), - receivedTimeMillis = System.currentTimeMillis(), - attachments = PointerAttachment.forPointers(Optional.of(attachments)) - ) - - SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get() - - ThreadUtil.sleep(1) - } - - private fun insertFailedMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) { - val attachments: List = (0 until attachmentCount).map { - attachment() - } - - val message = IncomingMediaMessage( - from = other.id, - body = body, - sentTimeMillis = System.currentTimeMillis(), - serverTimeMillis = System.currentTimeMillis(), - receivedTimeMillis = System.currentTimeMillis(), - attachments = PointerAttachment.forPointers(Optional.of(attachments)) - ) - - val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get() - - SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment -> - SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert.messageId) - } - - ThreadUtil.sleep(1) - } - - private fun insertFailedOutgoingMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) { - val attachments: List = (0 until attachmentCount).map { - attachment() - } - - val message = OutgoingMessage( - recipient = other, - body = body, - attachments = PointerAttachment.forPointers(Optional.of(attachments)), - timestamp = System.currentTimeMillis(), - isSecure = true - ) - - val insert = SignalDatabase.messages.insertMessageOutbox( - message, - SignalDatabase.threads.getOrCreateThreadIdFor(other), - false, - null - ) - - SignalDatabase.attachments.getAttachmentsForMessage(insert).forEachIndexed { index, attachment -> - SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert) - } - - ThreadUtil.sleep(1) - } - - private fun attachment(): SignalServiceAttachmentPointer { - return SignalServiceAttachmentPointer( - ReleaseChannel.CDN_NUMBER, - SignalServiceAttachmentRemoteId.from(""), - "image/webp", - null, - Optional.empty(), - Optional.empty(), - 1024, - 1024, - Optional.empty(), - Optional.of("/not-there.jpg"), - false, - false, - false, - Optional.empty(), - Optional.empty(), - System.currentTimeMillis() - ) } } diff --git a/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt b/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt new file mode 100644 index 0000000000..debfece9bd --- /dev/null +++ b/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt @@ -0,0 +1,189 @@ +package org.signal.benchmark.setup + +import org.thoughtcrime.securesms.attachments.PointerAttachment +import org.thoughtcrime.securesms.database.AttachmentTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.TestDbUtils +import org.thoughtcrime.securesms.mms.IncomingMediaMessage +import org.thoughtcrime.securesms.mms.OutgoingMessage +import org.thoughtcrime.securesms.mms.QuoteModel +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.releasechannel.ReleaseChannel +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId +import java.util.Collections +import java.util.Optional + +object TestMessages { + fun insertOutgoingTextMessage(other: Recipient, body: String, timestamp: Long = System.currentTimeMillis()) { + insertOutgoingMessage( + recipient = other, + message = OutgoingMessage( + recipient = other, + body = body, + timestamp = timestamp, + isSecure = true + ), + timestamp = timestamp + ) + } + + fun insertOutgoingImageMessage(other: Recipient, body: String? = null, attachmentCount: Int, timestamp: Long = System.currentTimeMillis()): Long { + val attachments: List = (0 until attachmentCount).map { + imageAttachment() + } + val message = OutgoingMessage( + recipient = other, + body = body, + attachments = PointerAttachment.forPointers(Optional.of(attachments)), + timestamp = timestamp, + isSecure = true + ) + return insertOutgoingMediaMessage(recipient = other, message = message, timestamp = timestamp) + } + + private fun insertOutgoingMediaMessage(recipient: Recipient, message: OutgoingMessage, timestamp: Long): Long { + val insert = insertOutgoingMessage(recipient, message = message, timestamp = timestamp) + setMessageMediaTransfered(insert) + + return insert + } + + private fun insertOutgoingMessage(recipient: Recipient, message: OutgoingMessage, timestamp: Long? = null): Long { + val insert = SignalDatabase.messages.insertMessageOutbox( + message, + SignalDatabase.threads.getOrCreateThreadIdFor(recipient), + false, + null + ) + if (timestamp != null) { + TestDbUtils.setMessageReceived(insert, timestamp) + } + SignalDatabase.messages.markAsSent(insert, true) + + return insert + } + fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) { + val message = IncomingMediaMessage( + from = other.id, + body = body, + sentTimeMillis = timestamp ?: System.currentTimeMillis(), + serverTimeMillis = timestamp ?: System.currentTimeMillis(), + receivedTimeMillis = timestamp ?: System.currentTimeMillis() + ) + + SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId + } + fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) { + val message = IncomingMediaMessage( + from = other.id, + body = body, + sentTimeMillis = timestamp ?: System.currentTimeMillis(), + serverTimeMillis = timestamp ?: System.currentTimeMillis(), + receivedTimeMillis = timestamp ?: System.currentTimeMillis(), + quote = quote + ) + insertIncomingMessage(other, message = message) + } + fun insertIncomingImageMessage(other: Recipient, body: String? = null, attachmentCount: Int, timestamp: Long? = null, failed: Boolean = false): Long { + val attachments: List = (0 until attachmentCount).map { + imageAttachment() + } + val message = IncomingMediaMessage( + from = other.id, + sentTimeMillis = timestamp ?: System.currentTimeMillis(), + serverTimeMillis = timestamp ?: System.currentTimeMillis(), + receivedTimeMillis = timestamp ?: System.currentTimeMillis(), + attachments = PointerAttachment.forPointers(Optional.of(attachments)) + ) + return insertIncomingMediaMessage(recipient = other, message = message, failed = failed) + } + + fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long { + val message = IncomingMediaMessage( + from = other.id, + sentTimeMillis = timestamp ?: System.currentTimeMillis(), + serverTimeMillis = timestamp ?: System.currentTimeMillis(), + receivedTimeMillis = timestamp ?: System.currentTimeMillis(), + attachments = PointerAttachment.forPointers(Optional.of(Collections.singletonList(voiceAttachment()) as List)) + ) + return insertIncomingMediaMessage(recipient = other, message = message, failed = false) + } + + private fun insertIncomingMediaMessage(recipient: Recipient, message: IncomingMediaMessage, failed: Boolean = false): Long { + val id = insertIncomingMessage(recipient = recipient, message = message) + if (failed) { + setMessageMediaFailed(id) + } else { + setMessageMediaTransfered(id) + } + + return id + } + + private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long { + return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId + } + + private fun setMessageMediaFailed(messageId: Long) { + SignalDatabase.attachments.getAttachmentsForMessage(messageId).forEachIndexed { index, attachment -> + SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, messageId) + } + } + + private fun setMessageMediaTransfered(messageId: Long) { + SignalDatabase.attachments.getAttachmentsForMessage(messageId).forEachIndexed { _, attachment -> + SignalDatabase.attachments.setTransferState(messageId, attachment.attachmentId, AttachmentTable.TRANSFER_PROGRESS_DONE) + } + } + private fun imageAttachment(): SignalServiceAttachmentPointer { + return SignalServiceAttachmentPointer( + ReleaseChannel.CDN_NUMBER, + SignalServiceAttachmentRemoteId.from(""), + "image/webp", + null, + Optional.empty(), + Optional.empty(), + 1024, + 1024, + Optional.empty(), + Optional.of("/not-there.jpg"), + false, + false, + false, + Optional.empty(), + Optional.empty(), + System.currentTimeMillis() + ) + } + + private fun voiceAttachment(): SignalServiceAttachmentPointer { + return SignalServiceAttachmentPointer( + ReleaseChannel.CDN_NUMBER, + SignalServiceAttachmentRemoteId.from(""), + "audio/aac", + null, + Optional.empty(), + Optional.empty(), + 1024, + 1024, + Optional.empty(), + Optional.of("/not-there.aac"), + true, + false, + false, + Optional.empty(), + Optional.empty(), + System.currentTimeMillis() + ) + } + + class TimestampGenerator(private var start: Long = System.currentTimeMillis()) { + fun nextTimestamp(): Long { + start += 500L + + return start + } + } +} diff --git a/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt b/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt new file mode 100644 index 0000000000..ed0c5ecfc6 --- /dev/null +++ b/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt @@ -0,0 +1,103 @@ +package org.signal.benchmark.setup + +import android.app.Application +import android.content.SharedPreferences +import android.preference.PreferenceManager +import org.signal.benchmark.DummyAccountManagerFactory +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.crypto.MasterSecretUtil +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor +import org.thoughtcrime.securesms.profiles.ProfileName +import org.thoughtcrime.securesms.push.AccountManagerFactory +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.registration.RegistrationData +import org.thoughtcrime.securesms.registration.RegistrationRepository +import org.thoughtcrime.securesms.registration.RegistrationUtil +import org.thoughtcrime.securesms.registration.VerifyResponse +import org.thoughtcrime.securesms.util.Util +import org.whispersystems.signalservice.api.profiles.SignalServiceProfile +import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.ServiceResponse +import org.whispersystems.signalservice.internal.ServiceResponseProcessor +import org.whispersystems.signalservice.internal.push.VerifyAccountResponse +import java.util.UUID + +object TestUsers { + + private var generatedOthers: Int = 0 + + fun setupSelf(): Recipient { + val application: Application = ApplicationDependencies.getApplication() + DeviceTransferBlockingInterceptor.getInstance().blockNetwork() + + PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit() + val masterSecret = MasterSecretUtil.generateMasterSecret(application, MasterSecretUtil.UNENCRYPTED_PASSPHRASE) + MasterSecretUtil.generateAsymmetricMasterSecret(application, masterSecret) + val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0) + preferences.edit().putBoolean("passphrase_initialized", true).commit() + + val registrationRepository = RegistrationRepository(application) + val registrationData = RegistrationData( + code = "123123", + e164 = "+15555550101", + password = Util.getSecret(18), + registrationId = registrationRepository.registrationId, + profileKey = registrationRepository.getProfileKey("+15555550101"), + fcmToken = "fcm-token", + pniRegistrationId = registrationRepository.pniRegistrationId, + recoveryPassword = "asdfasdfasdfasdf" + ) + val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null) + AccountManagerFactory.setInstance(DummyAccountManagerFactory()) + val response: ServiceResponse = registrationRepository.registerAccount( + registrationData, + verifyResponse, + false + ).blockingGet() + ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow + + SignalStore.kbsValues().optOut() + RegistrationUtil.maybeMarkRegistrationComplete() + SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) + + return Recipient.self() + } + + fun setupTestRecipient(): RecipientId { + return setupTestRecipients(1).first() + } + + fun setupTestRecipients(othersCount: Int): List { + val others = mutableListOf() + synchronized(this) { + if (generatedOthers + othersCount !in 0 until 1000) { + throw IllegalArgumentException("$othersCount must be between 0 and 1000") + } + + for (i in generatedOthers until generatedOthers + othersCount) { + val aci = ACI.from(UUID.randomUUID()) + val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i))) + SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i")) + SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew()) + SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true)) + SignalDatabase.recipients.setProfileSharing(recipientId, true) + SignalDatabase.recipients.markRegistered(recipientId, aci) + val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair() + ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey) + + others += recipientId + } + + generatedOthers += othersCount + } + + return others + } +} diff --git a/app/src/benchmark/java/org/thoughtcrime/securesms/database/TestDbUtils.kt b/app/src/benchmark/java/org/thoughtcrime/securesms/database/TestDbUtils.kt new file mode 100644 index 0000000000..ebcae98699 --- /dev/null +++ b/app/src/benchmark/java/org/thoughtcrime/securesms/database/TestDbUtils.kt @@ -0,0 +1,14 @@ +package org.thoughtcrime.securesms.database + +import android.content.ContentValues +import org.signal.core.util.SqlUtil.buildArgs + +object TestDbUtils { + + fun setMessageReceived(messageId: Long, timestamp: Long) { + val database: SQLiteDatabase = SignalDatabase.messages.databaseHelper.signalWritableDatabase + val contentValues = ContentValues() + contentValues.put(MessageTable.DATE_RECEIVED, timestamp) + val rowsUpdated = database.update(MessageTable.TABLE_NAME, contentValues, MessageTable.ID_WHERE, buildArgs(messageId)) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 0c27dd525b..2c96a41bc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -184,6 +184,7 @@ import org.thoughtcrime.securesms.util.RemoteDeleteUtil; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SignalLocalMetrics; import org.thoughtcrime.securesms.util.SignalProxyUtil; +import org.thoughtcrime.securesms.util.SignalTrace; import org.thoughtcrime.securesms.util.SnapToTopDataObserver; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.StorageUtil; @@ -290,6 +291,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect this.locale = Locale.getDefault(); startupStopwatch = new Stopwatch("conversation-open"); SignalLocalMetrics.ConversationOpen.start(); + SignalTrace.beginSection("ConversationOpen"); } @Override @@ -747,6 +749,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect startupStopwatch.stop(TAG); SignalLocalMetrics.ConversationOpen.onRenderFinished(); listener.onFirstRender(); + SignalTrace.endSection(); }); } }); diff --git a/benchmark/src/main/java/org/thoughtcrime/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/java/org/thoughtcrime/benchmark/BaselineProfileGenerator.kt index 3bb26d2f65..d10e04b192 100644 --- a/benchmark/src/main/java/org/thoughtcrime/benchmark/BaselineProfileGenerator.kt +++ b/benchmark/src/main/java/org/thoughtcrime/benchmark/BaselineProfileGenerator.kt @@ -23,22 +23,20 @@ class BaselineProfileGenerator { val baselineProfileRule = BaselineProfileRule() @Test - fun startup() = baselineProfileRule.collectBaselineProfile( - packageName = "org.thoughtcrime.securesms", - profileBlock = { - if (iteration == 0) { - val setupIntent = Intent().apply { - component = ComponentName("org.thoughtcrime.securesms", "org.signal.benchmark.BenchmarkSetupActivity") + fun startup() { + var setup = false + baselineProfileRule.collectBaselineProfile( + packageName = "org.thoughtcrime.securesms", + profileBlock = { + if (!setup) { + BenchmarkSetup.setup("cold-start", device) + setup = true } - startActivityAndWait(setupIntent) + startActivityAndWait() + device.findObject(By.textContains("Buddy")).click(); + device.wait(Until.hasObject(By.textContains("Signal message")), 10_000L) + device.wait(Until.hasObject(By.textContains("Test")), 5_000L) } - startActivityAndWait() - device.findObject(By.textContains("Buddy")).click(); - device.wait( - Until.hasObject(By.clazz("$packageName.conversation.ConversationActivity")), - 10000L - ) - device.wait(Until.hasObject(By.textContains("Test")), 10_000L) - } - ) + ) + } } diff --git a/benchmark/src/main/java/org/thoughtcrime/benchmark/BenchmarkSetup.kt b/benchmark/src/main/java/org/thoughtcrime/benchmark/BenchmarkSetup.kt new file mode 100644 index 0000000000..323736623f --- /dev/null +++ b/benchmark/src/main/java/org/thoughtcrime/benchmark/BenchmarkSetup.kt @@ -0,0 +1,12 @@ +package org.thoughtcrime.benchmark + +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until + +object BenchmarkSetup { + fun setup(type: String, device: UiDevice) { + device.executeShellCommand("am start -W -n org.thoughtcrime.securesms/org.signal.benchmark.BenchmarkSetupActivity --es setup-type $type") + device.wait(Until.hasObject(By.textContains("done")), 25_000L) + } +} \ No newline at end of file diff --git a/benchmark/src/main/java/org/thoughtcrime/benchmark/ConversationBenchmarks.kt b/benchmark/src/main/java/org/thoughtcrime/benchmark/ConversationBenchmarks.kt new file mode 100644 index 0000000000..7e6c9d3988 --- /dev/null +++ b/benchmark/src/main/java/org/thoughtcrime/benchmark/ConversationBenchmarks.kt @@ -0,0 +1,42 @@ +package org.thoughtcrime.benchmark + +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.ExperimentalMetricApi +import androidx.benchmark.macro.TraceSectionMetric +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ConversationBenchmarks { + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + @OptIn(ExperimentalMetricApi::class) + @Test + fun simpleConversationOpen() { + var setup = false + benchmarkRule.measureRepeated( + packageName = "org.thoughtcrime.securesms", + metrics = listOf(TraceSectionMetric("ConversationOpen")), + iterations = 10, + compilationMode = CompilationMode.Partial(), + setupBlock = { + if (!setup) { + BenchmarkSetup.setup("conversation-open", device) + setup = true + } + killProcess() + startActivityAndWait() + device.waitForIdle() + }) { + device.findObject(By.textContains("Buddy")).click(); + device.wait(Until.hasObject(By.textContains("Signal message")), 10_000L) + device.wait(Until.hasObject(By.textContains("Test")), 5_000L) + } + } +} \ No newline at end of file diff --git a/benchmark/src/main/java/org/thoughtcrime/benchmark/StartupBenchmarks.kt b/benchmark/src/main/java/org/thoughtcrime/benchmark/StartupBenchmarks.kt index 5cd6eca1dc..08fbcd0f92 100644 --- a/benchmark/src/main/java/org/thoughtcrime/benchmark/StartupBenchmarks.kt +++ b/benchmark/src/main/java/org/thoughtcrime/benchmark/StartupBenchmarks.kt @@ -10,6 +10,8 @@ import androidx.benchmark.macro.StartupTimingMetric import androidx.benchmark.macro.TraceSectionMetric import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -34,18 +36,9 @@ class StartupBenchmarks { measureStartup(5, CompilationMode.Partial()) } - private val fakeDataSetupBlock: MacrobenchmarkScope.() -> Unit = { - val setupIntent = Intent().apply { - component = ComponentName("org.thoughtcrime.securesms", "org.signal.benchmark.BenchmarkSetupActivity") - } - startActivityAndWait(setupIntent) - - killProcess() - dropKernelPageCache() - } - @OptIn(ExperimentalMetricApi::class) private fun measureStartup(iterations: Int, compilationMode: CompilationMode) { + var setup = false benchmarkRule.measureRepeated( packageName = "org.thoughtcrime.securesms", metrics = listOf(StartupTimingMetric(), TraceSectionMetric("ConversationListDataSource#load")), @@ -53,8 +46,12 @@ class StartupBenchmarks { startupMode = StartupMode.COLD, compilationMode = compilationMode, setupBlock = { - if (compilationMode !is CompilationMode.Partial || iteration == 0) { - fakeDataSetupBlock() + if (!setup) { + BenchmarkSetup.setup("cold-start", device) + + killProcess() + dropKernelPageCache() + setup = true } } ) {