mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-06-28 10:05:45 +01:00
Add a search benchmark.
This commit is contained in:
@@ -32,6 +32,14 @@ class BenchmarkSetupActivity : BaseActivity() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(BenchmarkSetupActivity::class)
|
||||
|
||||
const val SEARCH_KEYWORD = "lighthouse"
|
||||
|
||||
private val SEARCH_VOCABULARY = listOf(
|
||||
"hello", "world", "signal", "android", "kotlin", "database", "benchmark", "conversation",
|
||||
"morning", "evening", "weekend", "project", "meeting", "dinner", "coffee", "garden",
|
||||
"mountain", "river", "forest", "harbor", "market", "library", "concert", "holiday"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -51,6 +59,7 @@ class BenchmarkSetupActivity : BaseActivity() {
|
||||
when (intent.extras!!.getString("setup-type")) {
|
||||
"cold-start" -> setupColdStart()
|
||||
"conversation-open" -> setupConversationOpen()
|
||||
"conversation-list-search" -> setupConversationListSearch()
|
||||
"message-send" -> setupMessageSend()
|
||||
"group-message-send" -> setupGroupMessageSend()
|
||||
"group-delivery-receipt" -> setupGroupReceipt(includeMsl = true)
|
||||
@@ -97,6 +106,39 @@ class BenchmarkSetupActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupConversationListSearch() {
|
||||
TestUsers.setupSelf()
|
||||
|
||||
val recipientCount = 50
|
||||
val messagesPerRecipient = 2000
|
||||
val totalMessages = recipientCount * messagesPerRecipient
|
||||
val generator = TestMessages.TimestampGenerator(System.currentTimeMillis() - (totalMessages * 2000L) - 60_000L)
|
||||
|
||||
TestUsers.setupTestRecipients(recipientCount).forEachIndexed { recipientIndex, recipientId ->
|
||||
val recipient: Recipient = Recipient.resolved(recipientId)
|
||||
|
||||
for (i in 0 until messagesPerRecipient) {
|
||||
val body = searchableMessageBody(recipientIndex, i)
|
||||
if (i % 2 == 0) {
|
||||
TestMessages.insertIncomingTextMessage(other = recipient, body = body, timestamp = generator.nextTimestamp())
|
||||
} else {
|
||||
TestMessages.insertOutgoingTextMessage(other = recipient, body = body, timestamp = generator.nextTimestamp())
|
||||
}
|
||||
}
|
||||
|
||||
SignalDatabase.messages.setAllMessagesRead()
|
||||
SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchableMessageBody(recipientIndex: Int, messageIndex: Int): String {
|
||||
val words = SEARCH_VOCABULARY
|
||||
val w1 = words[(recipientIndex + messageIndex) % words.size]
|
||||
val w2 = words[(recipientIndex * 7 + messageIndex * 3) % words.size]
|
||||
val w3 = words[(recipientIndex * 13 + messageIndex * 5) % words.size]
|
||||
return "$w1 $w2 $SEARCH_KEYWORD $w3 message $messageIndex"
|
||||
}
|
||||
|
||||
private fun setupMessageSend() {
|
||||
TestUsers.setupSelf()
|
||||
TestUsers.setupTestClients(1)
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.SignalTrace;
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.concurrent.SerialExecutor;
|
||||
|
||||
@@ -82,26 +83,36 @@ public class SearchRepository {
|
||||
|
||||
@WorkerThread
|
||||
public @NonNull ThreadSearchResult queryThreadsSync(@NonNull String query, boolean unreadOnly) {
|
||||
long start = System.currentTimeMillis();
|
||||
List<ThreadWithRecipient> result = queryConversations(query, unreadOnly);
|
||||
SignalTrace.beginSection("ConversationListSearch-Threads");
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
List<ThreadWithRecipient> result = queryConversations(query, unreadOnly);
|
||||
|
||||
Log.d(TAG, "[threads] Search took " + (System.currentTimeMillis() - start) + " ms");
|
||||
Log.d(TAG, "[threads] Search took " + (System.currentTimeMillis() - start) + " ms");
|
||||
|
||||
return new ThreadSearchResult(result, query);
|
||||
return new ThreadSearchResult(result, query);
|
||||
} finally {
|
||||
SignalTrace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public @NonNull MessageSearchResult queryMessagesSync(@NonNull String query, @NonNull SearchFilter filter) {
|
||||
long start = System.currentTimeMillis();
|
||||
SignalTrace.beginSection("ConversationListSearch-Messages");
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
List<MessageResult> messages = queryMessages(query, filter);
|
||||
List<MessageResult> mentionMessages = queryMentions(convertMentionsQueryToTokens(query));
|
||||
List<MessageResult> filteredMentions = filterMentionResults(mentionMessages, filter);
|
||||
List<MessageResult> combined = mergeMessagesAndMentions(messages, filteredMentions);
|
||||
List<MessageResult> messages = queryMessages(query, filter);
|
||||
List<MessageResult> mentionMessages = queryMentions(convertMentionsQueryToTokens(query));
|
||||
List<MessageResult> filteredMentions = filterMentionResults(mentionMessages, filter);
|
||||
List<MessageResult> combined = mergeMessagesAndMentions(messages, filteredMentions);
|
||||
|
||||
Log.d(TAG, "[messages] Search took " + (System.currentTimeMillis() - start) + " ms");
|
||||
Log.d(TAG, "[messages] Search took " + (System.currentTimeMillis() - start) + " ms");
|
||||
|
||||
return new MessageSearchResult(combined, query);
|
||||
return new MessageSearchResult(combined, query);
|
||||
} finally {
|
||||
SignalTrace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
public void query(@NonNull String query, long threadId, @NonNull Callback<List<MessageResult>> callback) {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.benchmark
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
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
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Macrobenchmark for searching from the conversation list.
|
||||
*
|
||||
* Seeds 50 conversations with 2,000 messages each (100,000 messages total), then performs the same
|
||||
* operations the app runs when a user searches from the conversation list: opening the search
|
||||
* toolbar, typing a query, and waiting for results. Measures the full-text search against the
|
||||
* search table via the [SearchRepository] trace sections.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@RequiresApi(31)
|
||||
class SearchBenchmarks {
|
||||
@get:Rule
|
||||
val benchmarkRule = MacrobenchmarkRule()
|
||||
|
||||
@OptIn(ExperimentalMetricApi::class)
|
||||
@Test
|
||||
fun conversationListSearch() {
|
||||
var setup = false
|
||||
benchmarkRule.measureRepeated(
|
||||
packageName = "org.thoughtcrime.securesms.benchmark",
|
||||
metrics = listOf(
|
||||
TraceSectionMetric("ConversationListSearch-Messages", Mode.Sum),
|
||||
TraceSectionMetric("ConversationListSearch-Threads", Mode.Sum)
|
||||
),
|
||||
iterations = 3,
|
||||
compilationMode = CompilationMode.Partial(),
|
||||
setupBlock = {
|
||||
if (!setup) {
|
||||
BenchmarkSetup.setup("conversation-list-search", device, timeout = 600_000L)
|
||||
setup = true
|
||||
}
|
||||
killProcess()
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
device.executeShellCommand("pm grant $packageName ${Manifest.permission.POST_NOTIFICATIONS}")
|
||||
}
|
||||
startActivityAndWait()
|
||||
device.waitForIdle()
|
||||
}
|
||||
) {
|
||||
device.findObject(By.desc("Search")).click()
|
||||
|
||||
val searchField = device.wait(Until.findObject(By.clazz("android.widget.EditText")), 10_000L)
|
||||
searchField.text = SEARCH_QUERY
|
||||
|
||||
device.wait(Until.hasObject(By.textContains("Buddy")), 10_000L)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SEARCH_QUERY = "lighthouse"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user