Add a system for locally tracking performance on-device.

This commit is contained in:
Greyson Parrelli
2021-08-04 10:01:14 -04:00
committed by GitHub
parent c6c4988583
commit c131754874
15 changed files with 535 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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