Locally track message send time.

This commit is contained in:
Greyson Parrelli 2021-08-05 09:48:30 -04:00 committed by Alex Hart
parent 37ae740138
commit 784c373a0e
7 changed files with 132 additions and 4 deletions

View file

@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Projection; import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat; import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat; import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
@ -64,6 +65,8 @@ public class ConversationItemFooter extends ConstraintLayout {
private final Rect speedToggleHitRect = new Rect(); private final Rect speedToggleHitRect = new Rect();
private final int touchTargetSize = ViewUtil.dpToPx(48); private final int touchTargetSize = ViewUtil.dpToPx(48);
private long previousMessageId;
public ConversationItemFooter(Context context) { public ConversationItemFooter(Context context) {
super(context); super(context);
init(null); init(null);
@ -368,6 +371,19 @@ public class ConversationItemFooter extends ConstraintLayout {
} }
private void presentDeliveryStatus(@NonNull MessageRecord messageRecord) { private void presentDeliveryStatus(@NonNull MessageRecord messageRecord) {
long newMessageId = buildMessageId(messageRecord);
if (previousMessageId == newMessageId && deliveryStatusView.isPending() && !messageRecord.isPending()) {
if (messageRecord.getRecipient().isGroup()) {
SignalLocalMetrics.GroupMessageSend.onUiUpdated(messageRecord.getId());
} else {
SignalLocalMetrics.IndividualMessageSend.onUiUpdated(messageRecord.getId());
}
}
previousMessageId = newMessageId;
if (messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) { if (messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) {
deliveryStatusView.setNone(); deliveryStatusView.setNone();
return; return;
@ -426,6 +442,10 @@ public class ConversationItemFooter extends ConstraintLayout {
playbackSpeedToggleTextView.setVisibility(View.GONE); playbackSpeedToggleTextView.setVisibility(View.GONE);
} }
private long buildMessageId(@NonNull MessageRecord record) {
return record.isMms() ? -record.getId() : record.getId();
}
public interface OnTouchDelegateChangedListener { public interface OnTouchDelegateChangedListener {
void onTouchDelegateChanged(@NonNull Rect delegateRect, @NonNull View delegateView); void onTouchDelegateChanged(@NonNull Rect delegateRect, @NonNull View delegateView);
} }

View file

@ -60,6 +60,10 @@ public class DeliveryStatusView extends FrameLayout {
this.setVisibility(View.GONE); this.setVisibility(View.GONE);
} }
public boolean isPending() {
return pendingIndicator.getVisibility() == View.VISIBLE;
}
public void setPending() { public void setPending() {
this.setVisibility(View.VISIBLE); this.setVisibility(View.VISIBLE);
pendingIndicator.setVisibility(View.VISIBLE); pendingIndicator.setVisibility(View.VISIBLE);

View file

@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.RecipientAccessList; import org.thoughtcrime.securesms.util.RecipientAccessList;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.ContentHint;
@ -147,6 +148,8 @@ public final class PushGroupSendJob extends PushSendJob {
public void onPushSend() public void onPushSend()
throws IOException, MmsException, NoSuchMessageException, RetryLaterException throws IOException, MmsException, NoSuchMessageException, RetryLaterException
{ {
SignalLocalMetrics.GroupMessageSend.onJobStarted(messageId);
MessageDatabase database = DatabaseFactory.getMmsDatabase(context); MessageDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId); OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
long threadId = database.getMessageRecord(messageId).getThreadId(); long threadId = database.getMessageRecord(messageId).getThreadId();
@ -186,6 +189,7 @@ public final class PushGroupSendJob extends PushSendJob {
RecipientAccessList accessList = new RecipientAccessList(target); RecipientAccessList accessList = new RecipientAccessList(target);
List<SendMessageResult> results = deliver(message, groupRecipient, target); List<SendMessageResult> results = deliver(message, groupRecipient, target);
SignalLocalMetrics.GroupMessageSend.onNetworkFinished(messageId);
Log.i(TAG, JobLogger.format(this, "Finished send.")); Log.i(TAG, JobLogger.format(this, "Finished send."));
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(accessList.requireIdByAddress(result.getAddress()))).toList(); List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(accessList.requireIdByAddress(result.getAddress()))).toList();
@ -258,6 +262,8 @@ public final class PushGroupSendJob extends PushSendJob {
database.markAsSentFailed(messageId); database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId); notifyMediaMessageDeliveryFailed(context, messageId);
} }
SignalLocalMetrics.GroupMessageSend.onJobFinished(messageId);
} }
@Override @Override
@ -303,6 +309,7 @@ public final class PushGroupSendJob extends PushSendJob {
.withExpiration(groupRecipient.getExpiresInSeconds()) .withExpiration(groupRecipient.getExpiresInSeconds())
.asGroupMessage(group) .asGroupMessage(group)
.build(); .build();
SignalLocalMetrics.GroupMessageSend.onNetworkStarted(messageId);
return GroupSendUtil.sendResendableDataMessage(context, groupRecipient.requireGroupId().requireV2(), destinations, isRecipientUpdate, ContentHint.IMPLICIT, new MessageId(messageId, true), groupDataMessage); return GroupSendUtil.sendResendableDataMessage(context, groupRecipient.requireGroupId().requireV2(), destinations, isRecipientUpdate, ContentHint.IMPLICIT, new MessageId(messageId, true), groupDataMessage);
} else { } else {
throw new UndeliverableMessageException("Messages can no longer be sent to V1 groups!"); throw new UndeliverableMessageException("Messages can no longer be sent to V1 groups!");
@ -328,6 +335,7 @@ public final class PushGroupSendJob extends PushSendJob {
Log.i(TAG, JobLogger.format(this, "Beginning message send.")); Log.i(TAG, JobLogger.format(this, "Beginning message send."));
SignalLocalMetrics.GroupMessageSend.onNetworkStarted(messageId);
return GroupSendUtil.sendResendableDataMessage(context, return GroupSendUtil.sendResendableDataMessage(context,
groupRecipient.getGroupId().transform(GroupId::requireV2).orNull(), groupRecipient.getGroupId().transform(GroupId::requireV2).orNull(),
destinations, destinations,

View file

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -74,6 +75,8 @@ public class PushTextSendJob extends PushSendJob {
@Override @Override
public void onPushSend() throws IOException, NoSuchMessageException, UndeliverableMessageException, RetryLaterException { public void onPushSend() throws IOException, NoSuchMessageException, UndeliverableMessageException, RetryLaterException {
SignalLocalMetrics.IndividualMessageSend.onJobStarted(messageId);
ExpiringMessageManager expirationManager = ApplicationDependencies.getExpiringMessageManager(); ExpiringMessageManager expirationManager = ApplicationDependencies.getExpiringMessageManager();
MessageDatabase database = DatabaseFactory.getSmsDatabase(context); MessageDatabase database = DatabaseFactory.getSmsDatabase(context);
SmsMessageRecord record = database.getSmsMessage(messageId); SmsMessageRecord record = database.getSmsMessage(messageId);
@ -136,6 +139,8 @@ public class PushTextSendJob extends PushSendJob {
} catch (ProofRequiredException e) { } catch (ProofRequiredException e) {
handleProofRequiredException(e, record.getRecipient(), record.getThreadId(), messageId, false); handleProofRequiredException(e, record.getRecipient(), record.getThreadId(), messageId, false);
} }
SignalLocalMetrics.IndividualMessageSend.onJobFinished(messageId);
} }
@Override @Override
@ -176,11 +181,17 @@ public class PushTextSendJob extends PushSendJob {
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess); SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess);
SignalLocalMetrics.IndividualMessageSend.onNetworkStarted(messageId);
SendMessageResult result = messageSender.sendSyncMessage(syncMessage, syncAccess); SendMessageResult result = messageSender.sendSyncMessage(syncMessage, syncAccess);
SignalLocalMetrics.IndividualMessageSend.onNetworkFinished(messageId);
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false)); DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false));
return syncAccess.isPresent(); return syncAccess.isPresent();
} else { } else {
SignalLocalMetrics.IndividualMessageSend.onNetworkStarted(messageId);
SendMessageResult result = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.RESENDABLE, textSecureMessage); SendMessageResult result = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.RESENDABLE, textSecureMessage);
SignalLocalMetrics.IndividualMessageSend.onNetworkFinished(messageId);
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false)); DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false));
return result.getSuccess().isUnidentified(); return result.getSuccess().isUnidentified();
} }

View file

@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.ParcelUtil; import org.thoughtcrime.securesms.util.ParcelUtil;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.libsignal.util.guava.Preconditions; import org.whispersystems.libsignal.util.guava.Preconditions;
@ -114,6 +115,8 @@ public class MessageSender {
System.currentTimeMillis(), System.currentTimeMillis(),
insertListener); insertListener);
SignalLocalMetrics.IndividualMessageSend.start(messageId);
sendTextMessage(context, recipient, forceSms, keyExchange, messageId); sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
onMessageSent(); onMessageSent();
@ -135,6 +138,10 @@ public class MessageSender {
Recipient recipient = message.getRecipient(); Recipient recipient = message.getRecipient();
long messageId = database.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, recipient, message, allocatedThreadId), allocatedThreadId, forceSms, insertListener); long messageId = database.insertMessageOutbox(applyUniversalExpireTimerIfNecessary(context, recipient, message, allocatedThreadId), allocatedThreadId, forceSms, insertListener);
if (message.getRecipient().isGroup() && message.getAttachments().isEmpty() && message.getLinkPreviews().isEmpty() && message.getSharedContacts().isEmpty()) {
SignalLocalMetrics.GroupMessageSend.start(messageId);
}
sendMediaMessage(context, recipient, forceSms, messageId, Collections.emptyList()); sendMediaMessage(context, recipient, forceSms, messageId, Collections.emptyList());
onMessageSent(); onMessageSent();

View file

@ -26,8 +26,8 @@ import java.util.concurrent.Executor
object LocalMetrics { object LocalMetrics {
private val TAG: String = Log.tag(LocalMetrics::class.java) private val TAG: String = Log.tag(LocalMetrics::class.java)
private val eventsById: MutableMap<String, LocalMetricsEvent> = mutableMapOf() private val eventsById: MutableMap<String, LocalMetricsEvent> = LRUCache(200)
private val lastSplitTimeById: MutableMap<String, Long> = mutableMapOf() private val lastSplitTimeById: MutableMap<String, Long> = LRUCache(200)
private val executor: Executor = SignalExecutors.newCachedSingleThreadExecutor("signal-LocalMetrics") private val executor: Executor = SignalExecutors.newCachedSingleThreadExecutor("signal-LocalMetrics")
private val db: LocalMetricsDatabase by lazy { LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) } private val db: LocalMetricsDatabase by lazy { LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) }

View file

@ -25,8 +25,8 @@ public final class SignalLocalMetrics {
@MainThread @MainThread
public static void start() { public static void start() {
conversationListId = NAME_CONVERSATION_LIST + System.currentTimeMillis(); conversationListId = NAME_CONVERSATION_LIST + "-" + System.currentTimeMillis();
otherId = NAME_OTHER + System.currentTimeMillis(); otherId = NAME_OTHER + "-" + System.currentTimeMillis();
LocalMetrics.getInstance().start(conversationListId, NAME_CONVERSATION_LIST); LocalMetrics.getInstance().start(conversationListId, NAME_CONVERSATION_LIST);
LocalMetrics.getInstance().start(otherId, NAME_OTHER); LocalMetrics.getInstance().start(otherId, NAME_OTHER);
@ -63,4 +63,82 @@ public final class SignalLocalMetrics {
} }
} }
} }
public static final class IndividualMessageSend {
private static final String NAME = "individual-message-send";
private static final String SPLIT_JOB_ENQUEUE = "job-enqueue";
private static final String SPLIT_JOB_PRE_NETWORK = "job-pre-network";
private static final String SPLIT_NETWORK = "network";
private static final String SPLIT_JOB_POST_NETWORK = "job-post-network";
private static final String SPLIT_UI_UPDATE = "ui-update";
public static void start(long messageId) {
LocalMetrics.getInstance().start(buildId(messageId), NAME);
}
public static void onJobStarted(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_JOB_ENQUEUE);
}
public static void onNetworkStarted(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_JOB_PRE_NETWORK);
}
public static void onNetworkFinished(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_NETWORK);
}
public static void onJobFinished(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_JOB_POST_NETWORK);
}
public static void onUiUpdated(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_UI_UPDATE);
LocalMetrics.getInstance().end(buildId(messageId));
}
private static String buildId(long messageId) {
return NAME + "-" + messageId;
}
}
public static final class GroupMessageSend {
private static final String NAME = "group-message-send";
private static final String SPLIT_JOB_ENQUEUE = "job-enqueue";
private static final String SPLIT_JOB_PRE_NETWORK = "job-pre-network";
private static final String SPLIT_NETWORK = "network";
private static final String SPLIT_JOB_POST_NETWORK = "job-post-network";
private static final String SPLIT_UI_UPDATE = "ui-update";
public static void start(long messageId) {
LocalMetrics.getInstance().start(buildId(messageId), NAME);
}
public static void onJobStarted(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_JOB_ENQUEUE);
}
public static void onNetworkStarted(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_JOB_PRE_NETWORK);
}
public static void onNetworkFinished(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_NETWORK);
}
public static void onJobFinished(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_JOB_POST_NETWORK);
}
public static void onUiUpdated(long messageId) {
LocalMetrics.getInstance().split(buildId(messageId), SPLIT_UI_UPDATE);
LocalMetrics.getInstance().end(buildId(messageId));
}
private static String buildId(long messageId) {
return NAME + "-" + messageId;
}
}
} }