mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 18:30:20 +01:00
Add a system for locally tracking performance on-device.
This commit is contained in:
@@ -96,6 +96,7 @@ public final class AppStartup {
|
||||
if (outstandingCriticalRenderEvents == 0 && postRender.size() > 0) {
|
||||
Log.i(TAG, "Received first critical render event.");
|
||||
renderStartTime = System.currentTimeMillis();
|
||||
SignalLocalMetrics.ColdStart.onRenderStart();
|
||||
|
||||
postRenderHandler.removeCallbacksAndMessages(null);
|
||||
postRenderHandler.postDelayed(() -> {
|
||||
@@ -121,6 +122,7 @@ public final class AppStartup {
|
||||
|
||||
if (outstandingCriticalRenderEvents == 0 && postRender.size() > 0) {
|
||||
renderEndTime = System.currentTimeMillis();
|
||||
SignalLocalMetrics.ColdStart.onRenderFinished();
|
||||
|
||||
Log.i(TAG, "First render has finished. " +
|
||||
"Cold Start: " + (renderEndTime - applicationStartTime) + " ms, " +
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.LocalMetricsDatabase
|
||||
import org.thoughtcrime.securesms.database.model.LocalMetricsEvent
|
||||
import org.thoughtcrime.securesms.database.model.LocalMetricsSplit
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* A class for keeping track of local-only metrics.
|
||||
*
|
||||
* In particular, this class is geared towards timing events that continue over several code boundaries, e.g. sending a message.
|
||||
*
|
||||
* The process of tracking an event looks something like:
|
||||
* - start("mySpecialId", "send-message")
|
||||
* - split("mySpecialId", "enqueue-job")
|
||||
* - split("mySpecialId", "job-prep")
|
||||
* - split("mySpecialId", "network")
|
||||
* - split("mySpecialId", "ui-refresh")
|
||||
* - end("mySpecialId")
|
||||
*
|
||||
* These metrics are only ever included in debug logs in an aggregate fashion (i.e. p50, p90, p99) and are never automatically uploaded anywhere.
|
||||
*/
|
||||
object LocalMetrics {
|
||||
private val TAG: String = Log.tag(LocalMetrics::class.java)
|
||||
|
||||
private val eventsById: MutableMap<String, LocalMetricsEvent> = mutableMapOf()
|
||||
private val lastSplitTimeById: MutableMap<String, Long> = mutableMapOf()
|
||||
|
||||
private val executor: Executor = SignalExecutors.newCachedSingleThreadExecutor("signal-LocalMetrics")
|
||||
private val db: LocalMetricsDatabase by lazy { LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) }
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(): LocalMetrics {
|
||||
return LocalMetrics
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an event with the provided ID and name.
|
||||
*
|
||||
* @param id A constant that should be unique to this *specific event*. You'll use this same id when calling [split] and [end]. e.g. "message-send-1234"
|
||||
* @param name The name of the event. Does not need to be unique. e.g. "message-send"
|
||||
*/
|
||||
fun start(id: String, name: String) {
|
||||
val time = System.currentTimeMillis()
|
||||
|
||||
executor.execute {
|
||||
eventsById[id] = LocalMetricsEvent(
|
||||
createdAt = System.currentTimeMillis(),
|
||||
eventId = id,
|
||||
eventName = name,
|
||||
splits = mutableListOf()
|
||||
)
|
||||
lastSplitTimeById[id] = time
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a split for an event. The duration of the split will be the difference in time between either the event start or the last split, whichever is
|
||||
* applicable.
|
||||
*
|
||||
* If an event with the provided ID does not exist, this is effectively a no-op.
|
||||
*/
|
||||
fun split(id: String, split: String) {
|
||||
val time = System.currentTimeMillis()
|
||||
|
||||
executor.execute {
|
||||
val lastTime: Long? = lastSplitTimeById[id]
|
||||
|
||||
if (lastTime != null) {
|
||||
eventsById[id]?.splits?.add(LocalMetricsSplit(split, time - lastTime))
|
||||
lastSplitTimeById[id] = time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking an event you were previously tracking. All future calls to [split] and [end] will do nothing for this id.
|
||||
*/
|
||||
fun drop(id: String) {
|
||||
executor.execute {
|
||||
eventsById.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the event and flushes it to the database.
|
||||
*/
|
||||
fun end(id: String) {
|
||||
executor.execute {
|
||||
val event: LocalMetricsEvent? = eventsById[id]
|
||||
if (event != null) {
|
||||
db.insert(System.currentTimeMillis(), event)
|
||||
Log.d(TAG, event.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire local metrics store.
|
||||
*/
|
||||
fun clear() {
|
||||
executor.execute {
|
||||
Log.w(TAG, "Clearing local metrics store.")
|
||||
db.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
|
||||
/**
|
||||
* A nice interface for {@link LocalMetrics} that gives us a place to define string constants and nicer method names.
|
||||
*/
|
||||
public final class SignalLocalMetrics {
|
||||
|
||||
private SignalLocalMetrics() {}
|
||||
|
||||
public static final class ColdStart {
|
||||
private static final String NAME_CONVERSATION_LIST = "cold-start-conversation-list";
|
||||
private static final String NAME_OTHER = "cold-start-other";
|
||||
|
||||
private static final String SPLIT_APPLICATION_CREATE = "application-create";
|
||||
private static final String SPLIT_ACTIVITY_CREATE = "start-activity";
|
||||
private static final String SPLIT_DATA_LOADED = "data-loaded";
|
||||
private static final String SPLIT_RENDER = "render";
|
||||
|
||||
private static String conversationListId;
|
||||
private static String otherId;
|
||||
|
||||
private static boolean isConversationList;
|
||||
|
||||
@MainThread
|
||||
public static void start() {
|
||||
conversationListId = NAME_CONVERSATION_LIST + System.currentTimeMillis();
|
||||
otherId = NAME_OTHER + System.currentTimeMillis();
|
||||
|
||||
LocalMetrics.getInstance().start(conversationListId, NAME_CONVERSATION_LIST);
|
||||
LocalMetrics.getInstance().start(otherId, NAME_OTHER);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public static void onApplicationCreateFinished() {
|
||||
LocalMetrics.getInstance().split(conversationListId, SPLIT_APPLICATION_CREATE);
|
||||
LocalMetrics.getInstance().split(otherId, SPLIT_APPLICATION_CREATE);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public static void onRenderStart() {
|
||||
LocalMetrics.getInstance().split(conversationListId, SPLIT_ACTIVITY_CREATE);
|
||||
LocalMetrics.getInstance().split(otherId, SPLIT_ACTIVITY_CREATE);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public static void onConversationListDataLoaded() {
|
||||
isConversationList = true;
|
||||
LocalMetrics.getInstance().split(conversationListId, SPLIT_DATA_LOADED);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public static void onRenderFinished() {
|
||||
if (isConversationList) {
|
||||
LocalMetrics.getInstance().split(conversationListId, SPLIT_RENDER);
|
||||
LocalMetrics.getInstance().end(conversationListId);
|
||||
LocalMetrics.getInstance().drop(otherId);
|
||||
} else {
|
||||
LocalMetrics.getInstance().split(otherId, SPLIT_RENDER);
|
||||
LocalMetrics.getInstance().end(otherId);
|
||||
LocalMetrics.getInstance().drop(conversationListId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public class VersionTracker {
|
||||
SignalStore.misc().clearClientDeprecated();
|
||||
TextSecurePreferences.setLastVersionCode(context, currentVersionCode);
|
||||
ApplicationDependencies.getJobManager().add(new RemoteConfigRefreshJob());
|
||||
LocalMetrics.getInstance().clear();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new AssertionError(ioe);
|
||||
|
||||
Reference in New Issue
Block a user