SF: Add continuous transaction tracing
Add the ability to capture all incoming transactions into a ring
buffer. Capture transactions as the come in through the binder
thread. On the main thread push committed transactions to the
tracing thread which will then add to the buffer and purge
older entries as the buffer gets filled.
Also introduce a new flag to enable transaction tracing (disabled
by default).
Test: presubmit
Bug: 200284593
Change-Id: I90dd0fa4f89f68cfc7b7922ecd0bba4adab8d7a4
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index fb42cc0..10ab3b4 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -202,6 +202,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 93f8406..7a8968b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -357,10 +357,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()),
@@ -489,6 +490,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() {
@@ -1946,7 +1952,7 @@
}
if (mTracingEnabledChanged) {
- mTracingEnabled = mLayerTracing.isEnabled();
+ mLayerTracingEnabled = mLayerTracing.isEnabled();
mTracingEnabledChanged = false;
}
@@ -1964,7 +1970,7 @@
bool needsTraversal = false;
if (clearTransactionFlags(eTransactionFlushNeeded)) {
- needsTraversal = flushTransactionQueues();
+ needsTraversal = flushTransactionQueues(vsyncId);
}
const bool shouldCommit =
@@ -2095,7 +2101,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");
@@ -3374,10 +3380,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 {
@@ -3400,7 +3407,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
@@ -3486,12 +3493,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) {
@@ -3509,6 +3517,10 @@
std::move(transaction.transactionCommittedSignal));
}
}
+
+ if (mTransactionTracingEnabled) {
+ mTransactionTracing.addCommittedTransactions(transactions, vsyncId);
+ }
return needsTraversal;
}
@@ -3762,6 +3774,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.
@@ -4448,10 +4464,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 =
@@ -4650,8 +4668,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);
@@ -4848,7 +4867,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;
@@ -5049,6 +5067,8 @@
*/
mLayerTracing.dump(result);
result.append("\n");
+ mTransactionTracing.dump(result);
+ result.append("\n");
/*
* HWC layer minidump
@@ -5281,9 +5301,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;
}
@@ -5724,6 +5744,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 6093be9..56a90d1 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -63,6 +63,7 @@
#include "SurfaceFlingerFactory.h"
#include "TracedOrdinal.h"
#include "Tracing/LayerTracing.h"
+#include "Tracing/TransactionTracing.h"
#include "TransactionCallbackInvoker.h"
#include "TransactionState.h"
@@ -711,7 +712,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();
@@ -747,7 +748,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);
@@ -1082,7 +1084,7 @@
sp<StartPropertySetThread> mStartPropertySetThread;
surfaceflinger::Factory& mFactory;
-
+ pid_t mPid;
std::future<void> mRenderEnginePrimeCacheFuture;
// access must be protected by mStateLock
@@ -1091,6 +1093,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
@@ -1181,8 +1184,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 3dc6d8b..c0d2e99 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -93,6 +93,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 4c5789e..cd7c88e 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -380,7 +380,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