Merge "SF: Add continuous transaction tracing"
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index f16cd63..5560ed7 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -205,6 +205,7 @@
         "SurfaceFlingerDefaultFactory.cpp",
         "SurfaceInterceptor.cpp",
         "Tracing/LayerTracing.cpp",
+        "Tracing/TransactionTracing.cpp",
         "Tracing/TransactionProtoParser.cpp",
         "TransactionCallbackInvoker.cpp",
         "TunnelModeEnabledReporter.cpp",
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index daf5980..d67ba77 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -359,10 +359,11 @@
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
       : mFactory(factory),
+        mPid(getpid()),
         mInterceptor(mFactory.createSurfaceInterceptor()),
         mTimeStats(std::make_shared<impl::TimeStats>()),
         mFrameTracer(mFactory.createFrameTracer()),
-        mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, getpid())),
+        mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, mPid)),
         mCompositionEngine(mFactory.createCompositionEngine()),
         mHwcServiceName(base::GetProperty("debug.sf.hwc_service_name"s, "default"s)),
         mTunnelModeEnabledReporter(new TunnelModeEnabledReporter()),
@@ -491,6 +492,11 @@
     enableSdrDimming = property_get_bool("debug.sf.enable_sdr_dimming", enable_sdr_dimming(false));
 
     enableLatchUnsignaledConfig = getLatchUnsignaledConfig();
+
+    mTransactionTracingEnabled = property_get_bool("debug.sf.enable_transaction_tracing", false);
+    if (mTransactionTracingEnabled) {
+        mTransactionTracing.enable();
+    }
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -1989,7 +1995,7 @@
     }
 
     if (mTracingEnabledChanged) {
-        mTracingEnabled = mLayerTracing.isEnabled();
+        mLayerTracingEnabled = mLayerTracing.isEnabled();
         mTracingEnabledChanged = false;
     }
 
@@ -2007,7 +2013,7 @@
 
         bool needsTraversal = false;
         if (clearTransactionFlags(eTransactionFlushNeeded)) {
-            needsTraversal = flushTransactionQueues();
+            needsTraversal = flushTransactionQueues(vsyncId);
         }
 
         const bool shouldCommit =
@@ -2138,7 +2144,7 @@
     modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
 
     mLayersWithQueuedFrames.clear();
-    if (mTracingEnabled) {
+    if (mLayerTracingEnabled) {
         // This will block and should only be used for debugging.
         if (mVisibleRegionsDirty) {
             mLayerTracing.notify("visibleRegionsDirty");
@@ -3441,10 +3447,11 @@
         client->attachLayer(handle, lbc);
     }
 
+    int64_t transactionId = (((int64_t)mPid) << 32) | mUniqueTransactionId++;
     return setTransactionState(FrameTimelineInfo{}, states, displays, 0 /* flags */, nullptr,
                                InputWindowCommands{}, -1 /* desiredPresentTime */,
                                true /* isAutoTimestamp */, {}, false /* hasListenerCallbacks */, {},
-                               0 /* Undefined transactionId */);
+                               transactionId);
 }
 
 uint32_t SurfaceFlinger::getTransactionFlags() const {
@@ -3467,7 +3474,7 @@
     return old;
 }
 
-bool SurfaceFlinger::flushTransactionQueues() {
+bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) {
     // to prevent onHandleDestroyed from being called while the lock is held,
     // we must keep a copy of the transactions (specifically the composer
     // states) around outside the scope of the lock
@@ -3553,12 +3560,13 @@
                 ATRACE_INT("TransactionQueue", mTransactionQueue.size());
             }
 
-            return applyTransactions(transactions);
+            return applyTransactions(transactions, vsyncId);
         }
     }
 }
 
-bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions) {
+bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions,
+                                       int64_t vsyncId) {
     bool needsTraversal = false;
     // Now apply all transactions.
     for (const auto& transaction : transactions) {
@@ -3576,6 +3584,10 @@
                     std::move(transaction.transactionCommittedSignal));
         }
     }
+
+    if (mTransactionTracingEnabled) {
+        mTransactionTracing.addCommittedTransactions(transactions, vsyncId);
+    }
     return needsTraversal;
 }
 
@@ -3829,6 +3841,10 @@
     state.traverseStatesWithBuffers([&](const layer_state_t& state) {
         mBufferCountTracker.increment(state.surface->localBinder());
     });
+
+    if (mTransactionTracingEnabled) {
+        mTransactionTracing.addQueuedTransaction(state);
+    }
     queueTransaction(state);
 
     // Check the pending state to make sure the transaction is synchronous.
@@ -4517,10 +4533,12 @@
     displays.add(d);
 
     nsecs_t now = systemTime();
+
+    int64_t transactionId = (((int64_t)mPid) << 32) | mUniqueTransactionId++;
     // It should be on the main thread, apply it directly.
     applyTransactionState(FrameTimelineInfo{}, state, displays, 0, mInputWindowCommands,
                           /* desiredPresentTime */ now, true, {}, /* postTime */ now, true, false,
-                          {}, getpid(), getuid(), 0 /* Undefined transactionId */);
+                          {}, mPid, getuid(), transactionId);
 
     setPowerModeInternal(display, hal::PowerMode::ON);
     const nsecs_t vsyncPeriod =
@@ -4719,8 +4737,9 @@
 }
 
 status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) {
-    if (asProto && mLayerTracing.isEnabled()) {
+    if (asProto) {
         mLayerTracing.writeToFile();
+        mTransactionTracing.writeToFile();
     }
 
     return doDump(fd, DumpArgs(), asProto);
@@ -4917,7 +4936,6 @@
 }
 
 LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
-    // If context is SurfaceTracing thread, mTracingLock blocks display transactions on main thread.
     const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
 
     LayersProto layersProto;
@@ -5118,6 +5136,8 @@
      */
     mLayerTracing.dump(result);
     result.append("\n");
+    mTransactionTracing.dump(result);
+    result.append("\n");
 
     /*
      * HWC layer minidump
@@ -5350,9 +5370,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1040 are currently used for backdoors. The code
+    // Numbers from 1000 to 1041 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1040) {
+    if (code >= 1000 && code <= 1041) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -5791,6 +5811,20 @@
                 scheduleRepaint();
                 return NO_ERROR;
             }
+            case 1041: { // Transaction tracing
+                if (data.readInt32()) {
+                    // Transaction tracing is always running but allow the user to temporarily
+                    // increase the buffer when actively debugging.
+                    mTransactionTracing.setBufferSize(
+                            TransactionTracing::ACTIVE_TRACING_BUFFER_SIZE);
+                } else {
+                    mTransactionTracing.setBufferSize(
+                            TransactionTracing::CONTINUOUS_TRACING_BUFFER_SIZE);
+                    mTransactionTracing.writeToFile();
+                }
+                reply->writeInt32(NO_ERROR);
+                return NO_ERROR;
+            }
         }
     }
     return err;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7355130..2f274dc 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -64,6 +64,7 @@
 #include "SurfaceFlingerFactory.h"
 #include "TracedOrdinal.h"
 #include "Tracing/LayerTracing.h"
+#include "Tracing/TransactionTracing.h"
 #include "TransactionCallbackInvoker.h"
 #include "TransactionState.h"
 
@@ -712,7 +713,7 @@
                                int originPid, int originUid, uint64_t transactionId)
             REQUIRES(mStateLock);
     // flush pending transaction that was presented after desiredPresentTime.
-    bool flushTransactionQueues();
+    bool flushTransactionQueues(int64_t vsyncId);
     // Returns true if there is at least one transaction that needs to be flushed
     bool transactionFlushNeeded();
 
@@ -748,7 +749,8 @@
     bool allowedLatchUnsignaled() REQUIRES(mQueueLock, mStateLock);
     bool checkTransactionCanLatchUnsignaled(const TransactionState& transaction)
             REQUIRES(mStateLock);
-    bool applyTransactions(std::vector<TransactionState>& transactions) REQUIRES(mStateLock);
+    bool applyTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId)
+            REQUIRES(mStateLock);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
             REQUIRES(mStateLock);
@@ -1083,7 +1085,7 @@
 
     sp<StartPropertySetThread> mStartPropertySetThread;
     surfaceflinger::Factory& mFactory;
-
+    pid_t mPid;
     std::future<void> mRenderEnginePrimeCacheFuture;
 
     // access must be protected by mStateLock
@@ -1092,6 +1094,7 @@
     std::atomic<int32_t> mTransactionFlags = 0;
     std::vector<std::shared_ptr<CountDownLatch>> mTransactionCommittedSignals;
     bool mAnimTransactionPending = false;
+    std::atomic<uint32_t> mUniqueTransactionId = 1;
     SortedVector<sp<Layer>> mLayersPendingRemoval;
 
     // global color transform states
@@ -1182,8 +1185,10 @@
     sp<SurfaceInterceptor> mInterceptor;
 
     LayerTracing mLayerTracing{*this};
-    std::mutex mTracingLock;
-    bool mTracingEnabled = false;
+    bool mLayerTracingEnabled = false;
+
+    TransactionTracing mTransactionTracing;
+    bool mTransactionTracingEnabled = false;
     std::atomic<bool> mTracingEnabledChanged = false;
 
     const std::shared_ptr<TimeStats> mTimeStats;
diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/RingBuffer.h
index d0fb3f2..63a2786 100644
--- a/services/surfaceflinger/Tracing/RingBuffer.h
+++ b/services/surfaceflinger/Tracing/RingBuffer.h
@@ -21,8 +21,9 @@
 
 #include <log/log.h>
 #include <utils/Errors.h>
-#include <utils/SystemClock.h>
+#include <utils/Timers.h>
 #include <utils/Trace.h>
+#include <chrono>
 #include <queue>
 
 namespace android {
@@ -57,9 +58,7 @@
     status_t writeToFile(FileProto& fileProto, std::string filename) {
         ATRACE_CALL();
         std::string output;
-        flush(fileProto);
-        reset(mSizeInBytes);
-        if (!fileProto.SerializeToString(&output)) {
+        if (!writeToString(fileProto, &output)) {
             ALOGE("Could not serialize proto.");
             return UNKNOWN_ERROR;
         }
@@ -73,6 +72,13 @@
         return NO_ERROR;
     }
 
+    bool writeToString(FileProto& fileProto, std::string* outString) {
+        ATRACE_CALL();
+        flush(fileProto);
+        reset(mSizeInBytes);
+        return fileProto.SerializeToString(outString);
+    }
+
     std::vector<EntryProto> emplace(EntryProto&& proto) {
         std::vector<EntryProto> replacedEntries;
         size_t protoSize = static_cast<size_t>(proto.ByteSize());
@@ -99,8 +105,8 @@
         const int64_t durationCount = duration.count();
         base::StringAppendF(&result,
                             "  number of entries: %zu (%.2fMB / %.2fMB) duration: %" PRIi64 "ms\n",
-                            frameCount(), float(used()) / 1024.f * 1024.f,
-                            float(size()) / 1024.f * 1024.f, durationCount);
+                            frameCount(), float(used()) / (1024.f * 1024.f),
+                            float(size()) / (1024.f * 1024.f), durationCount);
     }
 
 private:
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
new file mode 100644
index 0000000..758bd31
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTracing"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+#include <utils/Trace.h>
+
+#include "RingBuffer.h"
+#include "TransactionTracing.h"
+
+namespace android {
+
+TransactionTracing::TransactionTracing() {
+    mBuffer = std::make_unique<
+            RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry>>();
+}
+
+TransactionTracing::~TransactionTracing() = default;
+
+bool TransactionTracing::enable() {
+    std::scoped_lock lock(mTraceLock);
+    if (mEnabled) {
+        return false;
+    }
+    mBuffer->setSize(mBufferSizeInBytes);
+    mEnabled = true;
+    {
+        std::scoped_lock lock(mMainThreadLock);
+        mDone = false;
+        mThread = std::thread(&TransactionTracing::loop, this);
+    }
+    return true;
+}
+
+bool TransactionTracing::disable() {
+    std::thread thread;
+    {
+        std::scoped_lock lock(mMainThreadLock);
+        mDone = true;
+        mTransactionsAvailableCv.notify_all();
+        thread = std::move(mThread);
+    }
+    if (thread.joinable()) {
+        thread.join();
+    }
+
+    std::scoped_lock lock(mTraceLock);
+    if (!mEnabled) {
+        return false;
+    }
+    mEnabled = false;
+
+    proto::TransactionTraceFile fileProto = createTraceFileProto();
+    mBuffer->writeToFile(fileProto, FILE_NAME);
+    mQueuedTransactions.clear();
+    return true;
+}
+
+bool TransactionTracing::isEnabled() const {
+    std::scoped_lock lock(mTraceLock);
+    return mEnabled;
+}
+
+status_t TransactionTracing::writeToFile() {
+    std::scoped_lock lock(mTraceLock);
+    if (!mEnabled) {
+        return STATUS_OK;
+    }
+    proto::TransactionTraceFile fileProto = createTraceFileProto();
+    return mBuffer->writeToFile(fileProto, FILE_NAME);
+}
+
+void TransactionTracing::setBufferSize(size_t bufferSizeInBytes) {
+    std::scoped_lock lock(mTraceLock);
+    mBufferSizeInBytes = bufferSizeInBytes;
+    mBuffer->setSize(mBufferSizeInBytes);
+}
+
+proto::TransactionTraceFile TransactionTracing::createTraceFileProto() const {
+    proto::TransactionTraceFile proto;
+    proto.set_magic_number(uint64_t(proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_H) << 32 |
+                           proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_L);
+    return proto;
+}
+
+void TransactionTracing::dump(std::string& result) const {
+    std::scoped_lock lock(mTraceLock);
+    base::StringAppendF(&result, "Transaction tracing state: %s\n",
+                        mEnabled ? "enabled" : "disabled");
+    base::StringAppendF(&result, "  queued transactions: %d\n",
+                        static_cast<uint32_t>(mQueuedTransactions.size()));
+    mBuffer->dump(result);
+}
+
+void TransactionTracing::addQueuedTransaction(const TransactionState& transaction) {
+    std::scoped_lock lock(mTraceLock);
+    ATRACE_CALL();
+    if (!mEnabled) {
+        return;
+    }
+    mQueuedTransactions[transaction.id] =
+            TransactionProtoParser::toProto(transaction, nullptr, nullptr);
+}
+
+void TransactionTracing::addCommittedTransactions(std::vector<TransactionState>& transactions,
+                                                  int64_t vsyncId) {
+    CommittedTransactions committedTransactions;
+    committedTransactions.vsyncId = vsyncId;
+    committedTransactions.timestamp = systemTime();
+    committedTransactions.transactionIds.reserve(transactions.size());
+    for (const auto& transaction : transactions) {
+        committedTransactions.transactionIds.emplace_back(transaction.id);
+    }
+
+    // Try to acquire the lock from main thread, but don't block if we cannot acquire the lock. Add
+    // it to pending transactions that we can collect later.
+    if (mMainThreadLock.try_lock()) {
+        // We got the lock! Collect any pending transactions and continue.
+        mCommittedTransactions.insert(mCommittedTransactions.end(),
+                                      std::make_move_iterator(mPendingTransactions.begin()),
+                                      std::make_move_iterator(mPendingTransactions.end()));
+        mPendingTransactions.clear();
+        mCommittedTransactions.emplace_back(committedTransactions);
+        mTransactionsAvailableCv.notify_one();
+        mMainThreadLock.unlock();
+    } else {
+        mPendingTransactions.emplace_back(committedTransactions);
+    }
+}
+
+void TransactionTracing::loop() {
+    while (true) {
+        std::vector<CommittedTransactions> committedTransactions;
+        {
+            std::unique_lock<std::mutex> lock(mMainThreadLock);
+            base::ScopedLockAssertion assumeLocked(mMainThreadLock);
+            mTransactionsAvailableCv.wait(lock, [&]() REQUIRES(mMainThreadLock) {
+                return mDone || !mCommittedTransactions.empty();
+            });
+            if (mDone) {
+                mCommittedTransactions.clear();
+                break;
+            }
+            committedTransactions = std::move(mCommittedTransactions);
+            mCommittedTransactions.clear();
+        } // unlock mMainThreadLock
+
+        addEntry(committedTransactions);
+
+        mTransactionsAddedToBufferCv.notify_one();
+    }
+}
+
+void TransactionTracing::addEntry(const std::vector<CommittedTransactions>& committedTransactions) {
+    ATRACE_CALL();
+    std::scoped_lock lock(mTraceLock);
+    std::vector<proto::TransactionTraceEntry> removedEntries;
+    for (const CommittedTransactions& entry : committedTransactions) {
+        proto::TransactionTraceEntry entryProto;
+        entryProto.set_elapsed_realtime_nanos(entry.timestamp);
+        entryProto.set_vsync_id(entry.vsyncId);
+        entryProto.mutable_transactions()->Reserve(
+                static_cast<int32_t>(entry.transactionIds.size()));
+        for (const uint64_t& id : entry.transactionIds) {
+            auto it = mQueuedTransactions.find(id);
+            if (it != mQueuedTransactions.end()) {
+                entryProto.mutable_transactions()->Add(std::move(it->second));
+                mQueuedTransactions.erase(it);
+            } else {
+                ALOGE("Could not find transaction id %" PRIu64, id);
+            }
+        }
+        mBuffer->emplace(std::move(entryProto));
+    }
+}
+
+void TransactionTracing::flush() {
+    std::unique_lock<std::mutex> lock(mMainThreadLock);
+    base::ScopedLockAssertion assumeLocked(mMainThreadLock);
+    mTransactionsAddedToBufferCv.wait(lock, [&]() REQUIRES(mMainThreadLock) {
+        return mCommittedTransactions.empty();
+    });
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
new file mode 100644
index 0000000..d92ab01
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <layerproto/TransactionProto.h>
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include "TransactionProtoParser.h"
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+template <typename FileProto, typename EntryProto>
+class RingBuffer;
+
+class SurfaceFlinger;
+class TransactionTracingTest;
+/*
+ * Records all committed transactions into a ring bufffer.
+ *
+ * Transactions come in via the binder thread. They are serialized to proto
+ * and stored in a map using the transaction id as key. Main thread will
+ * pass the list of transaction ids that are committed every vsync and notify
+ * the tracing thread. The tracing thread will then wake up and add the
+ * committed transactions to the ring buffer.
+ *
+ * When generating SF dump state, we will flush the buffer to a file which
+ * will then be included in the bugreport.
+ *
+ */
+class TransactionTracing {
+public:
+    TransactionTracing();
+    ~TransactionTracing();
+
+    bool enable();
+    bool disable();
+    bool isEnabled() const;
+
+    void addQueuedTransaction(const TransactionState&);
+    void addCommittedTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId);
+    status_t writeToFile();
+    void setBufferSize(size_t bufferSizeInBytes);
+    void dump(std::string&) const;
+    static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
+    static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
+
+private:
+    friend class TransactionTracingTest;
+
+    static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope";
+
+    mutable std::mutex mTraceLock;
+    bool mEnabled GUARDED_BY(mTraceLock) = false;
+    std::unique_ptr<RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry>> mBuffer
+            GUARDED_BY(mTraceLock);
+    size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = CONTINUOUS_TRACING_BUFFER_SIZE;
+    std::unordered_map<uint64_t, proto::TransactionState> mQueuedTransactions
+            GUARDED_BY(mTraceLock);
+
+    // We do not want main thread to block so main thread will try to acquire mMainThreadLock,
+    // otherwise will push data to temporary container.
+    std::mutex mMainThreadLock;
+    std::thread mThread GUARDED_BY(mMainThreadLock);
+    bool mDone GUARDED_BY(mMainThreadLock) = false;
+    std::condition_variable mTransactionsAvailableCv;
+    std::condition_variable mTransactionsAddedToBufferCv;
+    struct CommittedTransactions {
+        std::vector<uint64_t> transactionIds;
+        int64_t vsyncId;
+        int64_t timestamp;
+    };
+    std::vector<CommittedTransactions> mCommittedTransactions GUARDED_BY(mMainThreadLock);
+    std::vector<CommittedTransactions> mPendingTransactions; // only accessed by main thread
+    proto::TransactionTraceFile createTraceFileProto() const;
+
+    void loop();
+    void addEntry(const std::vector<CommittedTransactions>& committedTransactions)
+            EXCLUDES(mTraceLock);
+
+    // TEST
+    // Wait until all the committed transactions are added to the buffer.
+    void flush() EXCLUDES(mMainThreadLock);
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5f47a1a..561d9e2 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -94,6 +94,7 @@
         "TransactionFrameTracerTest.cpp",
         "TransactionProtoParserTest.cpp",
         "TransactionSurfaceFrameTest.cpp",
+        "TransactionTracingTest.cpp",
         "TunnelModeEnabledReporterTest.cpp",
         "StrongTypingTest.cpp",
         "VSyncDispatchTimerQueueTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 7f949b9..100a78d 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -388,7 +388,7 @@
                                              listenerCallbacks, transactionId);
     }
 
-    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(); };
+    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(0); };
 
     auto onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
         return mFlinger->onTransact(code, data, reply, flags);
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
new file mode 100644
index 0000000..2afa68a
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+#include "Tracing/RingBuffer.h"
+#include "Tracing/TransactionTracing.h"
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+class TransactionTracingTest : public testing::Test {
+protected:
+    std::unique_ptr<android::TransactionTracing> mTracing;
+
+    void SetUp() override { mTracing = std::make_unique<android::TransactionTracing>(); }
+
+    void TearDown() override { mTracing.reset(); }
+
+    auto getCommittedTransactions() {
+        std::scoped_lock<std::mutex> lock(mTracing->mMainThreadLock);
+        return mTracing->mCommittedTransactions;
+    }
+
+    auto getQueuedTransactions() {
+        std::scoped_lock<std::mutex> lock(mTracing->mTraceLock);
+        return mTracing->mQueuedTransactions;
+    }
+
+    auto getUsedBufferSize() {
+        std::scoped_lock<std::mutex> lock(mTracing->mTraceLock);
+        return mTracing->mBuffer->used();
+    }
+
+    auto flush() { return mTracing->flush(); }
+
+    auto bufferFront() {
+        std::scoped_lock<std::mutex> lock(mTracing->mTraceLock);
+        return mTracing->mBuffer->front();
+    }
+
+    bool threadIsJoinable() {
+        std::scoped_lock lock(mTracing->mMainThreadLock);
+        return mTracing->mThread.joinable();
+    }
+
+    std::string writeToString() {
+        std::scoped_lock<std::mutex> lock(mTracing->mTraceLock);
+        std::string output;
+        proto::TransactionTraceFile fileProto = mTracing->createTraceFileProto();
+        mTracing->mBuffer->writeToString(fileProto, &output);
+        return output;
+    }
+
+    // Test that we clean up the tracing thread and free any memory allocated.
+    void verifyDisabledTracingState() {
+        EXPECT_FALSE(mTracing->isEnabled());
+        EXPECT_FALSE(threadIsJoinable());
+        EXPECT_EQ(getCommittedTransactions().size(), 0u);
+        EXPECT_EQ(getQueuedTransactions().size(), 0u);
+        EXPECT_EQ(getUsedBufferSize(), 0u);
+    }
+
+    void verifyEntry(const proto::TransactionTraceEntry& actualProto,
+                     const std::vector<TransactionState> expectedTransactions,
+                     int64_t expectedVsyncId) {
+        EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId);
+        EXPECT_EQ(actualProto.transactions().size(),
+                  static_cast<int32_t>(expectedTransactions.size()));
+        for (uint32_t i = 0; i < expectedTransactions.size(); i++) {
+            EXPECT_EQ(actualProto.transactions(static_cast<int32_t>(i)).pid(),
+                      expectedTransactions[i].originPid);
+        }
+    }
+};
+
+TEST_F(TransactionTracingTest, enable) {
+    EXPECT_FALSE(mTracing->isEnabled());
+    mTracing->enable();
+    EXPECT_TRUE(mTracing->isEnabled());
+    mTracing->disable();
+    verifyDisabledTracingState();
+}
+
+TEST_F(TransactionTracingTest, addTransactions) {
+    mTracing->enable();
+    std::vector<TransactionState> transactions;
+    transactions.reserve(100);
+    for (uint64_t i = 0; i < 100; i++) {
+        TransactionState transaction;
+        transaction.id = i;
+        transaction.originPid = static_cast<int32_t>(i);
+        transactions.emplace_back(transaction);
+        mTracing->addQueuedTransaction(transaction);
+    }
+
+    // Split incoming transactions into two and commit them in reverse order to test out of order
+    // commits.
+    std::vector<TransactionState> firstTransactionSet =
+            std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
+    int64_t firstTransactionSetVsyncId = 42;
+    mTracing->addCommittedTransactions(firstTransactionSet, firstTransactionSetVsyncId);
+
+    int64_t secondTransactionSetVsyncId = 43;
+    std::vector<TransactionState> secondTransactionSet =
+            std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
+    mTracing->addCommittedTransactions(secondTransactionSet, secondTransactionSetVsyncId);
+    flush();
+
+    std::string protoString = writeToString();
+    proto::TransactionTraceFile proto;
+    proto.ParseFromString(protoString);
+    EXPECT_EQ(proto.entry().size(), 2);
+    verifyEntry(proto.entry(0), firstTransactionSet, firstTransactionSetVsyncId);
+    verifyEntry(proto.entry(1), secondTransactionSet, secondTransactionSetVsyncId);
+
+    mTracing->disable();
+    verifyDisabledTracingState();
+}
+
+} // namespace android