From 831cd2f297bb8355d52a99ebed3b5705dfe9194e Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 4 Dec 2020 18:18:08 -0500 Subject: [PATCH] Trace database methods. --- app/build.gradle | 2 +- .../database/FlipperSqlCipherAdapter.java | 4 +- .../securesms/tracing/TracerImpl.java | 193 ---------- .../database/AttachmentDatabase.java | 2 - .../securesms/database/DatabaseFactory.java | 4 +- .../securesms/database/DraftDatabase.java | 2 - .../securesms/database/GroupDatabase.java | 2 - .../database/GroupReceiptDatabase.java | 2 - .../securesms/database/IdentityDatabase.java | 2 - .../securesms/database/JobDatabase.java | 2 - .../securesms/database/KeyValueDatabase.java | 2 - .../securesms/database/MediaDatabase.java | 2 - .../securesms/database/MegaphoneDatabase.java | 2 - .../securesms/database/MentionDatabase.java | 2 - .../securesms/database/MessageDatabase.java | 1 - .../securesms/database/MmsDatabase.java | 1 - .../securesms/database/MmsSmsDatabase.java | 1 - .../database/OneTimePreKeyDatabase.java | 2 - .../securesms/database/PushDatabase.java | 2 - .../securesms/database/RecipientDatabase.java | 2 - .../database/RemappedRecordsDatabase.java | 1 - .../securesms/database/SQLiteDatabase.java | 332 ++++++++++++++++++ .../securesms/database/SearchDatabase.java | 1 - .../securesms/database/SessionDatabase.java | 2 - .../database/SignedPreKeyDatabase.java | 2 - .../securesms/database/SmsDatabase.java | 1 - .../securesms/database/SmsMigrator.java | 1 - .../securesms/database/StickerDatabase.java | 2 - .../database/StorageKeyDatabase.java | 2 - .../securesms/database/ThreadDatabase.java | 2 - .../database/helpers/SQLCipherOpenHelper.java | 8 +- .../logsubmit/SubmitDebugLogRepository.java | 4 +- .../logsubmit/SubmitDebugLogViewModel.java | 5 +- .../securesms/tracing/Tracer.java | 238 +++++++++++-- app/src/{internal => main}/proto/Trace.proto | 0 .../securesms/tracing/TracerImpl.java | 34 -- .../securesms/tracing/TracerImpl.java | 34 -- 37 files changed, 554 insertions(+), 347 deletions(-) delete mode 100644 app/src/internal/java/org/thoughtcrime/securesms/tracing/TracerImpl.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/SQLiteDatabase.java rename app/src/{internal => main}/proto/Trace.proto (100%) delete mode 100644 app/src/play/java/org/thoughtcrime/securesms/tracing/TracerImpl.java delete mode 100644 app/src/website/java/org/thoughtcrime/securesms/tracing/TracerImpl.java diff --git a/app/build.gradle b/app/build.gradle index b0164708a0..d74108b956 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,7 +128,7 @@ android { buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\"" buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" - buildConfigField "int", "TRACE_EVENT_MAX", "2000" + buildConfigField "int", "TRACE_EVENT_MAX", "3500" ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' diff --git a/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java b/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java index c660f644a8..08a20e263f 100644 --- a/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java +++ b/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java @@ -247,11 +247,11 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver threadPackets; - private final Queue eventPackets; - private final AtomicInteger eventCount; - - TracerImpl() { - this.clock = SystemClock::elapsedRealtimeNanos; - this.threadPackets = new ConcurrentHashMap<>(); - this.eventPackets = new ConcurrentLinkedQueue<>(); - this.eventCount = new AtomicInteger(0); - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public void start(@NonNull String methodName) { - long time = clock.getTimeNanos(); - Thread currentThread = Thread.currentThread(); - - if (!threadPackets.containsKey(currentThread.getId())) { - threadPackets.put(currentThread.getId(), forThread(currentThread)); - } - - addPacket(forMethodStart(methodName, time, currentThread.getId())); - } - - @Override - public void start(@NonNull String methodName, @NonNull String key, @NonNull String value) { - long time = clock.getTimeNanos(); - Thread currentThread = Thread.currentThread(); - - if (!threadPackets.containsKey(currentThread.getId())) { - threadPackets.put(currentThread.getId(), forThread(currentThread)); - } - - addPacket(forMethodStart(methodName, time, currentThread.getId(), key, value)); - } - - @Override - public void end(@NonNull String methodName) { - addPacket(forMethodEnd(methodName, clock.getTimeNanos(), Thread.currentThread().getId())); - } - - public @NonNull byte[] serialize() { - Trace.Builder trace = Trace.newBuilder(); - - for (TracePacket thread : threadPackets.values()) { - trace.addPacket(thread); - } - - for (TracePacket event : eventPackets) { - trace.addPacket(event); - } - - trace.addPacket(forSynchronization(clock.getTimeNanos())); - - return trace.build().toByteArray(); - } - - /** - * Attempts to add a packet to our list while keeping the size of our circular buffer in-check. - * The tracking of the event count is not perfectly thread-safe, but doing it in a thread-safe - * way would likely involve adding a lock, which we really don't want to do, since it'll add - * unnecessary overhead. - * - * Note that we keep track of the event count separately because - * {@link ConcurrentLinkedQueue#size()} is NOT a constant-time operation. - */ - private void addPacket(@NonNull TracePacket packet) { - eventPackets.add(packet); - - int size = eventCount.incrementAndGet(); - - for (int i = size; i > BuildConfig.TRACE_EVENT_MAX; i--) { - eventPackets.poll(); - eventCount.decrementAndGet(); - } - } - - private static TracePacket forThread(@NonNull Thread thread) { - return TracePacket.newBuilder() - .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) - .setTrackDescriptor(TrackDescriptor.newBuilder() - .setUuid(thread.getId()) - .setName(thread.getName())) - .build(); - - } - - private static TracePacket forMethodStart(@NonNull String name, long time, long threadId) { - return TracePacket.newBuilder() - .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) - .setTimestamp(time) - .setTrackEvent(TrackEvent.newBuilder() - .setTrackUuid(threadId) - .setName(name) - .setType(TrackEvent.Type.TYPE_SLICE_BEGIN)) - .build(); - } - - private static TracePacket forMethodStart(@NonNull String name, long time, long threadId, @NonNull String key, @NonNull String value) { - return TracePacket.newBuilder() - .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) - .setTimestamp(time) - .setTrackEvent(TrackEvent.newBuilder() - .setTrackUuid(threadId) - .setName(name) - .setType(TrackEvent.Type.TYPE_SLICE_BEGIN) - .addDebugAnnotations(TraceProtos.DebugAnnotation.newBuilder() - .setName(key) - .setStringValue(value) - .build())) - .build(); - } - - private static TracePacket forMethodEnd(@NonNull String name, long time, long threadId) { - return TracePacket.newBuilder() - .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) - .setTimestamp(time) - .setTrackEvent(TrackEvent.newBuilder() - .setTrackUuid(threadId) - .setName(name) - .setType(TrackEvent.Type.TYPE_SLICE_END)) - .build(); - } - - private static TracePacket forSynchronization(long time) { - return TracePacket.newBuilder() - .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) - .setTimestamp(time) - .setSynchronizationMarker(ByteString.copyFrom(SYNCHRONIZATION_MARKER)) - .build(); - } - - private interface Clock { - long getTimeNanos(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index a4d56e0a2b..f6abdf87a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -34,8 +34,6 @@ import com.bumptech.glide.Glide; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import net.sqlcipher.database.SQLiteDatabase; - import org.json.JSONArray; import org.json.JSONException; import org.thoughtcrime.securesms.attachments.Attachment; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index 94ec86bdaf..d834a4251d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -171,7 +171,7 @@ public class DatabaseFactory { } public static SQLiteDatabase getBackupDatabase(Context context) { - return getInstance(context).databaseHelper.getReadableDatabase(); + return getInstance(context).databaseHelper.getReadableDatabase().getSqlCipherDatabase(); } public static void upgradeRestored(Context context, SQLiteDatabase database){ @@ -241,7 +241,7 @@ public class DatabaseFactory { SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyOpenHelper.getWritableDatabase(), - databaseHelper.getWritableDatabase(), + databaseHelper.getWritableDatabase().getSqlCipherDatabase(), listener); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java index 034790dea6..39cf0c96ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -8,8 +8,6 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.tracing.Trace; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 7434b66507..8b23f264c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -13,8 +13,6 @@ import androidx.annotation.WorkerThread; import com.annimon.stream.Stream; import com.google.protobuf.InvalidProtocolBufferException; -import net.sqlcipher.database.SQLiteDatabase; - import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.Member; import org.signal.storageservice.protos.groups.local.DecryptedGroup; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index c7c91040e1..14663f6bb7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -7,8 +7,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.tracing.Trace; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java index ddce2bee30..a3a618eb85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -23,8 +23,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.identity.IdentityRecordList; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java index 9c8e9fbf3c..d8eca56724 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -7,8 +7,6 @@ import androidx.annotation.NonNull; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java index 81bb9d0ad0..9874e0604a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java @@ -6,8 +6,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet; import org.thoughtcrime.securesms.tracing.Trace; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index f70ce897b3..b292df8c8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -7,8 +7,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.recipients.RecipientId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java index e52cf1c6e5..24f73aa87a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java @@ -6,8 +6,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MegaphoneRecord; import org.thoughtcrime.securesms.logging.Log; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MentionDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MentionDatabase.java index e6d27e37ee..d93b1a4bc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MentionDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MentionDatabase.java @@ -10,8 +10,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.recipients.RecipientId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index f40e27196a..8a114984e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -12,7 +12,6 @@ import com.annimon.stream.Stream; import com.google.android.mms.pdu_alt.NotificationInd; import com.google.protobuf.InvalidProtocolBufferException; -import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; import org.thoughtcrime.securesms.database.documents.Document; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 1ae0d1b8cf..390b97d1f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -29,7 +29,6 @@ import com.annimon.stream.Stream; import com.google.android.mms.pdu_alt.NotificationInd; import com.google.android.mms.pdu_alt.PduHeaders; -import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; import org.json.JSONArray; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 7a17a4e9c4..c4a5f3a4a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -24,7 +24,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteQueryBuilder; import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyDatabase.java index 1f59110f8d..015488b2ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyDatabase.java @@ -7,8 +7,6 @@ import android.database.Cursor; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.logging.Log; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libsignal.InvalidKeyException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java index 8d5708ddb7..da1f2de47d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java @@ -6,8 +6,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.tracing.Trace; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 58d4017b32..566eda86bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -12,7 +12,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteConstraintException; -import net.sqlcipher.database.SQLiteDatabase; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.zkgroup.groups.GroupMasterKey; @@ -35,7 +34,6 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileName; -import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.storage.StorageSyncHelper; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecordsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecordsDatabase.java index f2353f6d28..3d37845a91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecordsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecordsDatabase.java @@ -6,7 +6,6 @@ import android.content.Context; import androidx.annotation.NonNull; import net.sqlcipher.Cursor; -import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.recipients.RecipientId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SQLiteDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SQLiteDatabase.java new file mode 100644 index 0000000000..ab5194bf28 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SQLiteDatabase.java @@ -0,0 +1,332 @@ +package org.thoughtcrime.securesms.database; + + +import android.content.ContentValues; + +import androidx.annotation.Nullable; + +import com.google.android.gms.vision.Tracker; + +import net.sqlcipher.Cursor; +import net.sqlcipher.SQLException; +import net.sqlcipher.database.SQLiteQueryStats; +import net.sqlcipher.database.SQLiteStatement; +import net.sqlcipher.database.SQLiteTransactionListener; + +import org.thoughtcrime.securesms.tracing.Tracer; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * This is a wrapper around {@link net.sqlcipher.database.SQLiteDatabase}. There's difficulties + * making a subclass, so instead we just match the interface. Callers should just need to change + * their import statements. + */ +public class SQLiteDatabase { + + public static final int CONFLICT_ROLLBACK = 1; + public static final int CONFLICT_ABORT = 2; + public static final int CONFLICT_FAIL = 3; + public static final int CONFLICT_IGNORE = 4; + public static final int CONFLICT_REPLACE = 5; + public static final int CONFLICT_NONE = 0; + + private static final String KEY_QUERY = "query"; + private static final String KEY_TABLE = "table"; + private static final String KEY_THREAD = "thread"; + private static final String NAME_LOCK = "LOCK"; + + private final net.sqlcipher.database.SQLiteDatabase wrapped; + private final Tracer tracer; + + public SQLiteDatabase(net.sqlcipher.database.SQLiteDatabase wrapped) { + this.wrapped = wrapped; + this.tracer = Tracer.getInstance(); + } + + private void traceLockStart() { + tracer.start(NAME_LOCK, Tracer.TrackId.DB_LOCK, KEY_THREAD, Thread.currentThread().getName()); + } + + private void traceLockEnd() { + tracer.end(NAME_LOCK, Tracer.TrackId.DB_LOCK); + } + + private void trace(String methodName, Runnable runnable) { + tracer.start(methodName); + runnable.run(); + tracer.end(methodName); + } + + private void traceSql(String methodName, String query, boolean locked, Runnable returnable) { + if (locked) { + traceLockStart(); + } + + tracer.start(methodName, KEY_QUERY, query); + returnable.run(); + tracer.end(methodName); + + if (locked) { + traceLockEnd(); + } + } + + private E traceSql(String methodName, String query, boolean locked, Returnable returnable) { + return traceSql(methodName, null, query, locked, returnable); + } + + private E traceSql(String methodName, String table, String query, boolean locked, Returnable returnable) { + if (locked) { + traceLockStart(); + } + + Map params = new HashMap<>(); + if (query != null) { + params.put(KEY_QUERY, query); + } + if (table != null) { + params.put(KEY_TABLE, table); + } + + tracer.start(methodName, params); + E result = returnable.run(); + tracer.end(methodName); + + if (locked) { + traceLockEnd(); + } + + return result; + } + + public net.sqlcipher.database.SQLiteDatabase getSqlCipherDatabase() { + return wrapped; + } + + private interface Returnable { + E run(); + } + + + // ======================================================= + // Traced + // ======================================================= + + public void beginTransaction() { + traceLockStart(); + trace("beginTransaction()", wrapped::beginTransaction); + } + + public void endTransaction() { + trace("endTransaction()", wrapped::endTransaction); + traceLockEnd(); + } + + public void setTransactionSuccessful() { + trace("setTransactionSuccessful()", wrapped::setTransactionSuccessful); + } + + public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { + return traceSql("query(9)", table, selection, false, () -> wrapped.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)); + } + + public Cursor queryWithFactory(net.sqlcipher.database.SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { + return traceSql("queryWithFactory()", table, selection, false, () -> wrapped.queryWithFactory(cursorFactory, distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)); + } + + public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) { + return traceSql("query(7)", table, selection, false, () -> wrapped.query(table, columns, selection, selectionArgs, groupBy, having, orderBy)); + } + + public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { + return traceSql("query(8)", table, selection, false, () -> wrapped.query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)); + } + + public Cursor rawQuery(String sql, String[] selectionArgs) { + return traceSql("rawQuery(2a)", sql, false, () -> wrapped.rawQuery(sql, selectionArgs)); + } + + public Cursor rawQuery(String sql, Object[] args) { + return traceSql("rawQuery(2b)", sql, false,() -> wrapped.rawQuery(sql, args)); + } + + public Cursor rawQueryWithFactory(net.sqlcipher.database.SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable) { + return traceSql("rawQueryWithFactory()", sql, false, () -> wrapped.rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable)); + } + + public Cursor rawQuery(String sql, String[] selectionArgs, int initialRead, int maxRead) { + return traceSql("rawQuery(4)", sql, false, () -> rawQuery(sql, selectionArgs, initialRead, maxRead)); + } + + public long insert(String table, String nullColumnHack, ContentValues values) { + return traceSql("insert()", table, null, true, () -> wrapped.insert(table, nullColumnHack, values)); + } + + public long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { + return traceSql("insertOrThrow()", table, null, true, () -> wrapped.insertOrThrow(table, nullColumnHack, values)); + } + + public long replace(String table, String nullColumnHack, ContentValues initialValues) { + return traceSql("replace()", table, null, true,() -> wrapped.replace(table, nullColumnHack, initialValues)); + } + + public long replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues) throws SQLException { + return traceSql("replaceOrThrow()", table, null, true, () -> wrapped.replaceOrThrow(table, nullColumnHack, initialValues)); + } + + public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { + return traceSql("insertWithOnConflict()", table, null, true, () -> wrapped.insertWithOnConflict(table, nullColumnHack, initialValues, conflictAlgorithm)); + } + + public int delete(String table, String whereClause, String[] whereArgs) { + return traceSql("delete()", table, whereClause, true, () -> wrapped.delete(table, whereClause, whereArgs)); + } + + public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return traceSql("update()", table, whereClause, true, () -> wrapped.update(table, values, whereClause, whereArgs)); + } + + public int updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) { + return traceSql("updateWithOnConflict()", table, whereClause, true, () -> wrapped.updateWithOnConflict(table, values, whereClause, whereArgs, conflictAlgorithm)); + } + + public void execSQL(String sql) throws SQLException { + traceSql("execSQL(1)", sql, true, () -> wrapped.execSQL(sql)); + } + + public void rawExecSQL(String sql) { + traceSql("rawExecSQL()", sql, true, () -> wrapped.rawExecSQL(sql)); + } + + public void execSQL(String sql, Object[] bindArgs) throws SQLException { + traceSql("execSQL(2)", sql, true, () -> wrapped.execSQL(sql, bindArgs)); + } + + + // ======================================================= + // Ignored + // ======================================================= + + public boolean enableWriteAheadLogging() { + return wrapped.enableWriteAheadLogging(); + } + + public void disableWriteAheadLogging() { + wrapped.disableWriteAheadLogging(); + } + + public boolean isWriteAheadLoggingEnabled() { + return wrapped.isWriteAheadLoggingEnabled(); + } + + public void setForeignKeyConstraintsEnabled(boolean enable) { + wrapped.setForeignKeyConstraintsEnabled(enable); + } + + public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { + wrapped.beginTransactionWithListener(transactionListener); + } + + public void beginTransactionNonExclusive() { + wrapped.beginTransactionNonExclusive(); + } + + public void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener) { + wrapped.beginTransactionWithListenerNonExclusive(transactionListener); + } + + public boolean inTransaction() { + return wrapped.inTransaction(); + } + + public boolean isDbLockedByCurrentThread() { + return wrapped.isDbLockedByCurrentThread(); + } + + public boolean isDbLockedByOtherThreads() { + return wrapped.isDbLockedByOtherThreads(); + } + + public boolean yieldIfContendedSafely() { + return wrapped.yieldIfContendedSafely(); + } + + public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { + return wrapped.yieldIfContendedSafely(sleepAfterYieldDelay); + } + + public int getVersion() { + return wrapped.getVersion(); + } + + public void setVersion(int version) { + wrapped.setVersion(version); + } + + public long getMaximumSize() { + return wrapped.getMaximumSize(); + } + + public long setMaximumSize(long numBytes) { + return wrapped.setMaximumSize(numBytes); + } + + public long getPageSize() { + return wrapped.getPageSize(); + } + + public void setPageSize(long numBytes) { + wrapped.setPageSize(numBytes); + } + + public SQLiteStatement compileStatement(String sql) throws SQLException { + return wrapped.compileStatement(sql); + } + + public SQLiteQueryStats getQueryStats(String sql, Object[] args) { + return wrapped.getQueryStats(sql, args); + } + + public boolean isReadOnly() { + return wrapped.isReadOnly(); + } + + public boolean isOpen() { + return wrapped.isOpen(); + } + + public boolean needUpgrade(int newVersion) { + return wrapped.needUpgrade(newVersion); + } + + public final String getPath() { + return wrapped.getPath(); + } + + public void setLocale(Locale locale) { + wrapped.setLocale(locale); + } + + public boolean isInCompiledSqlCache(String sql) { + return wrapped.isInCompiledSqlCache(sql); + } + + public void purgeFromCompiledSqlCache(String sql) { + wrapped.purgeFromCompiledSqlCache(sql); + } + + public void resetCompiledSqlCache() { + wrapped.resetCompiledSqlCache(); + } + + public int getMaxSqlCacheSize() { + return wrapped.getMaxSqlCacheSize(); + } + + public void setMaxSqlCacheSize(int cacheSize) { + wrapped.setMaxSqlCacheSize(cacheSize); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java index a874e8d660..1ef587a6a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java @@ -8,7 +8,6 @@ import androidx.annotation.NonNull; import com.annimon.stream.Stream; import net.sqlcipher.Cursor; -import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.tracing.Trace; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java index 3045094a48..22d7f482c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java @@ -7,8 +7,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.RecipientId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyDatabase.java index f180bbde94..73b8518510 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyDatabase.java @@ -7,8 +7,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.tracing.Trace; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index b345bdd532..a1274d031d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -28,7 +28,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; import com.google.android.mms.pdu_alt.NotificationInd; -import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java index 833582ff21..27b7dff109 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -25,7 +25,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; import org.thoughtcrime.securesms.groups.GroupId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java index c03d3dc387..114ab556af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java @@ -9,8 +9,6 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java index 3ebe79c813..41434b6774 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java @@ -7,8 +7,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.tracing.Trace; import org.thoughtcrime.securesms.util.Base64; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 02e17b834e..f53459f6e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -29,8 +29,6 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; import com.fasterxml.jackson.annotation.JsonProperty; -import net.sqlcipher.database.SQLiteDatabase; - import org.jsoup.helper.StringUtil; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 1a650ccbf3..934227ab16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -1246,12 +1246,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { Log.i(TAG, "Upgrade complete. Took " + (System.currentTimeMillis() - startTime) + " ms."); } - public SQLiteDatabase getReadableDatabase() { - return getReadableDatabase(databaseSecret.asString()); + public org.thoughtcrime.securesms.database.SQLiteDatabase getReadableDatabase() { + return new org.thoughtcrime.securesms.database.SQLiteDatabase(getReadableDatabase(databaseSecret.asString())); } - public SQLiteDatabase getWritableDatabase() { - return getWritableDatabase(databaseSecret.asString()); + public org.thoughtcrime.securesms.database.SQLiteDatabase getWritableDatabase() { + return new org.thoughtcrime.securesms.database.SQLiteDatabase(getWritableDatabase(databaseSecret.asString())); } public void markCurrent(SQLiteDatabase db) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java index 49f25e7e24..93de1da811 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java @@ -62,9 +62,7 @@ public class SubmitDebugLogRepository { add(new LogSectionCapabilities()); add(new LogSectionFeatureFlags()); add(new LogSectionPermissions()); - if (Tracer.getInstance().isEnabled()) { - add(new LogSectionTrace()); - } + add(new LogSectionTrace()); add(new LogSectionThreads()); add(new LogSectionLogcat()); add(new LogSectionLogger()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java index 414d305a94..9541460f56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java @@ -30,10 +30,7 @@ public class SubmitDebugLogViewModel extends ViewModel { this.repo = new SubmitDebugLogRepository(); this.lines = new DefaultValueLiveData<>(Collections.emptyList()); this.mode = new MutableLiveData<>(); - - if (Tracer.getInstance().isEnabled()) { - this.trace = Tracer.getInstance().serialize(); - } + this.trace = Tracer.getInstance().serialize(); repo.getLogLines(result -> { sourceLines = result; diff --git a/app/src/main/java/org/thoughtcrime/securesms/tracing/Tracer.java b/app/src/main/java/org/thoughtcrime/securesms/tracing/Tracer.java index 83e8c2157c..b5665e4119 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/tracing/Tracer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/tracing/Tracer.java @@ -1,45 +1,229 @@ package org.thoughtcrime.securesms.tracing; +import android.os.SystemClock; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.protobuf.ByteString; + +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.trace.TraceProtos; +import org.thoughtcrime.securesms.trace.TraceProtos.Trace; +import org.thoughtcrime.securesms.trace.TraceProtos.TracePacket; +import org.thoughtcrime.securesms.trace.TraceProtos.TrackDescriptor; +import org.thoughtcrime.securesms.trace.TraceProtos.TrackEvent; +import org.whispersystems.signalservice.api.util.UuidUtil; + +import java.util.Collections; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; /** - * A class to create Perfetto-compatible traces. + * A class to create Perfetto-compatible traces. Currently keeps the entire trace in memory to + * avoid weirdness with synchronizing to disk. + * + * Some general info on how the Perfetto format works: + * - The file format is just a Trace proto (see Trace.proto) + * - The Trace proto is just a series of TracePackets + * - TracePackets can describe: + * - Threads + * - Start of a method + * - End of a method + * - (And a bunch of other stuff that's not relevant to use at this point) + * + * We keep a circular buffer of TracePackets for method calls, and we keep a separate list of + * TracePackets for threads so we don't lose any of those. + * + * Serializing is just a matter of throwing all the TracePackets we have into a proto. + * + * Note: This class aims to be largely-thread-safe, but prioritizes speed and memory efficiency + * above all else. These methods are going to be called very quickly from every thread imaginable, + * and we want to create as little overhead as possible. The idea being that it's ok if we don't, + * for example, keep a perfect circular buffer size if it allows us to reduce overhead. The only + * cost of screwing up would be dropping a trace packet or something, which, while sad, won't affect + * how the app functions. */ -public interface Tracer { +public final class Tracer { - TracerImpl INSTANCE = new TracerImpl(); + public static final class TrackId { + public static final long DB_LOCK = -8675309; - static @NonNull Tracer getInstance() { + private static final String DB_LOCK_NAME = "Database Lock"; + } + + private static final Tracer INSTANCE = new Tracer(); + + private static final int TRUSTED_SEQUENCE_ID = 1; + private static final byte[] SYNCHRONIZATION_MARKER = UuidUtil.toByteArray(UUID.fromString("82477a76-b28d-42ba-81dc-33326d57a079")); + private static final long SYNCHRONIZATION_INTERVAL = TimeUnit.SECONDS.toNanos(3); + + private final Clock clock; + private final Map threadPackets; + private final Queue eventPackets; + private final AtomicInteger eventCount; + + private long lastSyncTime; + + Tracer() { + this.clock = SystemClock::elapsedRealtimeNanos; + this.threadPackets = new ConcurrentHashMap<>(); + this.eventPackets = new ConcurrentLinkedQueue<>(); + this.eventCount = new AtomicInteger(0); + } + + public static @NonNull Tracer getInstance() { return INSTANCE; } - /** - * True if enabled, otherwise false. - */ - boolean isEnabled(); + public void start(@NonNull String methodName) { + start(methodName, Thread.currentThread().getId(), null); + } + + public void start(@NonNull String methodName, long trackId) { + start(methodName, trackId, null); + } + + public void start(@NonNull String methodName, @NonNull String key, @Nullable String value) { + start(methodName, Thread.currentThread().getId(), key, value); + } + + public void start(@NonNull String methodName, long trackId, @NonNull String key, @Nullable String value) { + start(methodName, trackId, Collections.singletonMap(key, value)); + } + + public void start(@NonNull String methodName, @Nullable Map values) { + start(methodName, Thread.currentThread().getId(), values); + } + + public void start(@NonNull String methodName, long trackId, @Nullable Map values) { + long time = clock.getTimeNanos(); + + if (time - lastSyncTime > SYNCHRONIZATION_INTERVAL) { + addPacket(forSynchronization(time)); + lastSyncTime = time; + } + + if (!threadPackets.containsKey(trackId)) { + threadPackets.put(trackId, forTrackId(trackId)); + } + + addPacket(forMethodStart(methodName, time, trackId, values)); + } + + public void end(@NonNull String methodName) { + addPacket(forMethodEnd(methodName, clock.getTimeNanos(), Thread.currentThread().getId())); + } + + public void end(@NonNull String methodName, long trackId) { + addPacket(forMethodEnd(methodName, clock.getTimeNanos(), trackId)); + } + + public @NonNull byte[] serialize() { + Trace.Builder trace = Trace.newBuilder(); + + for (TracePacket thread : threadPackets.values()) { + trace.addPacket(thread); + } + + for (TracePacket event : eventPackets) { + trace.addPacket(event); + } + + trace.addPacket(forSynchronization(clock.getTimeNanos())); + + return trace.build().toByteArray(); + } /** - * Marks the start of a method call. Always follow this with a call to {@link #end(String)}. - */ - void start(@NonNull String methodName); - - /** - * Marks the start of a method call. Always follow this with a call to {@link #end(String)}. + * Attempts to add a packet to our list while keeping the size of our circular buffer in-check. + * The tracking of the event count is not perfectly thread-safe, but doing it in a thread-safe + * way would likely involve adding a lock, which we really don't want to do, since it'll add + * unnecessary overhead. * - * Includes the ability to pass a key-value pair that will be shown in the trace when you click - * on the slice. + * Note that we keep track of the event count separately because + * {@link ConcurrentLinkedQueue#size()} is NOT a constant-time operation. */ - void start(@NonNull String methodName, @NonNull String key, @NonNull String value); + private void addPacket(@NonNull TracePacket packet) { + eventPackets.add(packet); - /** - * Marks the end of a method call. - */ - void end(@NonNull String methodName); + int size = eventCount.incrementAndGet(); - /** - * Serializes the current state of the trace to a Perfetto-compatible byte array. Note that - * there's no locking here, and therefore tracing will continue. We're just grabbing a best-effort - * snapshot. - */ - @NonNull byte[] serialize(); + for (int i = size; i > BuildConfig.TRACE_EVENT_MAX; i--) { + eventPackets.poll(); + eventCount.decrementAndGet(); + } + } + + private TracePacket forTrackId(long id) { + if (id == TrackId.DB_LOCK) { + return forTrack(id, TrackId.DB_LOCK_NAME); + } else { + Thread currentThread = Thread.currentThread(); + return forTrack(currentThread.getId(), currentThread.getName()); + } + } + + private static TracePacket forTrack(long id, String name) { + return TracePacket.newBuilder() + .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) + .setTrackDescriptor(TrackDescriptor.newBuilder() + .setUuid(id) + .setName(name)) + .build(); + + } + + private static TracePacket forMethodStart(@NonNull String name, long time, long threadId, @Nullable Map values) { + TrackEvent.Builder event = TrackEvent.newBuilder() + .setTrackUuid(threadId) + .setName(name) + .setType(TrackEvent.Type.TYPE_SLICE_BEGIN); + + if (values != null) { + for (Map.Entry entry : values.entrySet()) { + event.addDebugAnnotations(debugAnnotation(entry.getKey(), entry.getValue())); + } + } + + return TracePacket.newBuilder() + .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) + .setTimestamp(time) + .setTrackEvent(event) + .build(); + } + + private static TraceProtos.DebugAnnotation debugAnnotation(@NonNull String key, @Nullable String value) { + return TraceProtos.DebugAnnotation.newBuilder() + .setName(key) + .setStringValue(value != null ? value : "") + .build(); + } + + private static TracePacket forMethodEnd(@NonNull String name, long time, long threadId) { + return TracePacket.newBuilder() + .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) + .setTimestamp(time) + .setTrackEvent(TrackEvent.newBuilder() + .setTrackUuid(threadId) + .setName(name) + .setType(TrackEvent.Type.TYPE_SLICE_END)) + .build(); + } + + private static TracePacket forSynchronization(long time) { + return TracePacket.newBuilder() + .setTrustedPacketSequenceId(TRUSTED_SEQUENCE_ID) + .setTimestamp(time) + .setSynchronizationMarker(ByteString.copyFrom(SYNCHRONIZATION_MARKER)) + .build(); + } + + private interface Clock { + long getTimeNanos(); + } } diff --git a/app/src/internal/proto/Trace.proto b/app/src/main/proto/Trace.proto similarity index 100% rename from app/src/internal/proto/Trace.proto rename to app/src/main/proto/Trace.proto diff --git a/app/src/play/java/org/thoughtcrime/securesms/tracing/TracerImpl.java b/app/src/play/java/org/thoughtcrime/securesms/tracing/TracerImpl.java deleted file mode 100644 index b356f7a31f..0000000000 --- a/app/src/play/java/org/thoughtcrime/securesms/tracing/TracerImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.thoughtcrime.securesms.tracing; - -import androidx.annotation.NonNull; - -/** - * Dummy implementation. - */ -final class TracerImpl implements Tracer { - - @Override - public boolean isEnabled() { - return false; - } - - @Override - public void start(@NonNull String methodName) { - throw new UnsupportedOperationException(); - } - - @Override - public void start(@NonNull String methodName, @NonNull String key, @NonNull String value) { - throw new UnsupportedOperationException(); - } - - @Override - public void end(@NonNull String methodName) { - throw new UnsupportedOperationException(); - } - - @Override - public @NonNull byte[] serialize() { - throw new UnsupportedOperationException(); - } -} diff --git a/app/src/website/java/org/thoughtcrime/securesms/tracing/TracerImpl.java b/app/src/website/java/org/thoughtcrime/securesms/tracing/TracerImpl.java deleted file mode 100644 index b356f7a31f..0000000000 --- a/app/src/website/java/org/thoughtcrime/securesms/tracing/TracerImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.thoughtcrime.securesms.tracing; - -import androidx.annotation.NonNull; - -/** - * Dummy implementation. - */ -final class TracerImpl implements Tracer { - - @Override - public boolean isEnabled() { - return false; - } - - @Override - public void start(@NonNull String methodName) { - throw new UnsupportedOperationException(); - } - - @Override - public void start(@NonNull String methodName, @NonNull String key, @NonNull String value) { - throw new UnsupportedOperationException(); - } - - @Override - public void end(@NonNull String methodName) { - throw new UnsupportedOperationException(); - } - - @Override - public @NonNull byte[] serialize() { - throw new UnsupportedOperationException(); - } -}