From: Dan Stoza Date: Fri, 13 May 2016 18:37:28 +0000 (-0700) Subject: BufferQueue/SF: Add OccupancyTracker X-Git-Tag: android-x86-7.1-r1~238^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=e77c7669bee30b7c0099172cf0c38cef92412040;p=android-x86%2Fframeworks-native.git BufferQueue/SF: Add OccupancyTracker Adds an OccupancyTracker to BufferQueue. This module keeps track of how many buffers are in the queue over time, which, in combination with various aggregation of these statistics, allows SurfaceFlinger to report what fraction of the time a given layer was double- or triple-buffered. Change-Id: Ida6e967dc5483c00a633e9fe03998e420dd88502 --- diff --git a/include/gui/BufferQueueConsumer.h b/include/gui/BufferQueueConsumer.h index b2daae47c3..a9fce1ab5d 100644 --- a/include/gui/BufferQueueConsumer.h +++ b/include/gui/BufferQueueConsumer.h @@ -136,6 +136,10 @@ public: // Retrieve the sideband buffer stream, if any. virtual sp getSidebandStream() const; + // See IGraphicBufferConsumer::getOccupancyHistory + virtual status_t getOccupancyHistory(bool forceFlush, + std::vector* outHistory) override; + // dump our state in a String virtual void dump(String8& result, const char* prefix) const; diff --git a/include/gui/BufferQueueCore.h b/include/gui/BufferQueueCore.h index 4337da9b04..6c69d69c42 100644 --- a/include/gui/BufferQueueCore.h +++ b/include/gui/BufferQueueCore.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -322,6 +323,8 @@ private: // The slot of the last queued buffer int mLastQueuedSlot; + OccupancyTracker mOccupancyTracker; + }; // class BufferQueueCore } // namespace android diff --git a/include/gui/ConsumerBase.h b/include/gui/ConsumerBase.h index 9307a26fb3..d1f4cdd7d0 100644 --- a/include/gui/ConsumerBase.h +++ b/include/gui/ConsumerBase.h @@ -85,6 +85,10 @@ public: // See IGraphicBufferConsumer::setDefaultBufferDataSpace status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace); + // See IGraphicBufferConsumer::getOccupancyHistory + status_t getOccupancyHistory(bool forceFlush, + std::vector* outHistory); + private: ConsumerBase(const ConsumerBase&); void operator=(const ConsumerBase&); diff --git a/include/gui/IGraphicBufferConsumer.h b/include/gui/IGraphicBufferConsumer.h index e983c160a5..4915478837 100644 --- a/include/gui/IGraphicBufferConsumer.h +++ b/include/gui/IGraphicBufferConsumer.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -265,6 +266,12 @@ public: // Retrieve the sideband buffer stream, if any. virtual sp getSidebandStream() const = 0; + // Retrieves any stored segments of the occupancy history of this + // BufferQueue and clears them. Optionally closes out the pending segment if + // forceFlush is true. + virtual status_t getOccupancyHistory(bool forceFlush, + std::vector* outHistory) = 0; + // dump state into a string virtual void dump(String8& result, const char* prefix) const = 0; diff --git a/include/gui/OccupancyTracker.h b/include/gui/OccupancyTracker.h new file mode 100644 index 0000000000..1d15e7f029 --- /dev/null +++ b/include/gui/OccupancyTracker.h @@ -0,0 +1,104 @@ +/* + * Copyright 2016 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. + */ + + +#ifndef ANDROID_GUI_OCCUPANCYTRACKER_H +#define ANDROID_GUI_OCCUPANCYTRACKER_H + +#include + +#include + +#include +#include + +namespace android { + +class String8; + +class OccupancyTracker +{ +public: + OccupancyTracker() + : mPendingSegment(), + mSegmentHistory(), + mLastOccupancy(0), + mLastOccupancyChangeTime(0) {} + + struct Segment : public Parcelable { + Segment() + : totalTime(0), + numFrames(0), + occupancyAverage(0.0f), + usedThirdBuffer(false) {} + + Segment(nsecs_t totalTime, size_t numFrames, float occupancyAverage, + bool usedThirdBuffer) + : totalTime(totalTime), + numFrames(numFrames), + occupancyAverage(occupancyAverage), + usedThirdBuffer(usedThirdBuffer) {} + + // Parcelable interface + virtual status_t writeToParcel(Parcel* parcel) const override; + virtual status_t readFromParcel(const Parcel* parcel) override; + + nsecs_t totalTime; + size_t numFrames; + + // Average occupancy of the queue over this segment. (0.0, 1.0) implies + // double-buffered, (1.0, 2.0) implies triple-buffered. + float occupancyAverage; + + // Whether a third buffer was used at all during this segment (since a + // segment could read as double-buffered on average, but still require a + // third buffer to avoid jank for some smaller portion) + bool usedThirdBuffer; + }; + + void registerOccupancyChange(size_t occupancy); + std::vector getSegmentHistory(bool forceFlush); + +private: + static constexpr size_t MAX_HISTORY_SIZE = 10; + static constexpr nsecs_t NEW_SEGMENT_DELAY = ms2ns(100); + static constexpr size_t LONG_SEGMENT_THRESHOLD = 3; + + struct PendingSegment { + void clear() { + totalTime = 0; + numFrames = 0; + mOccupancyTimes.clear(); + } + + nsecs_t totalTime; + size_t numFrames; + std::unordered_map mOccupancyTimes; + }; + + void recordPendingSegment(); + + PendingSegment mPendingSegment; + std::deque mSegmentHistory; + + size_t mLastOccupancy; + nsecs_t mLastOccupancyChangeTime; + +}; // class OccupancyTracker + +} // namespace android + +#endif diff --git a/libs/gui/Android.mk b/libs/gui/Android.mk index 6e92a471ee..3e30bb2cf3 100644 --- a/libs/gui/Android.mk +++ b/libs/gui/Android.mk @@ -64,6 +64,7 @@ LOCAL_SRC_FILES := \ ISurfaceComposer.cpp \ ISurfaceComposerClient.cpp \ LayerState.cpp \ + OccupancyTracker.cpp \ Sensor.cpp \ SensorEventQueue.cpp \ SensorManager.cpp \ diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp index cbc88932ac..e8860d12fe 100644 --- a/libs/gui/BufferQueueConsumer.cpp +++ b/libs/gui/BufferQueueConsumer.cpp @@ -260,6 +260,7 @@ status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, mCore->mDequeueCondition.broadcast(); ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size()); + mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size()); VALIDATE_CONSISTENCY(); } @@ -717,6 +718,13 @@ sp BufferQueueConsumer::getSidebandStream() const { return mCore->mSidebandStream; } +status_t BufferQueueConsumer::getOccupancyHistory(bool forceFlush, + std::vector* outHistory) { + Mutex::Autolock lock(mCore->mMutex); + *outHistory = mCore->mOccupancyTracker.getSegmentHistory(forceFlush); + return NO_ERROR; +} + void BufferQueueConsumer::dump(String8& result, const char* prefix) const { const IPCThreadState* ipc = IPCThreadState::self(); const pid_t pid = ipc->getCallingPid(); diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 3e26e058fb..65f8255dec 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -886,6 +886,7 @@ status_t BufferQueueProducer::queueBuffer(int slot, static_cast(mCore->mQueue.size())); ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size()); + mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size()); // Take a ticket for the callback functions callbackTicket = mNextCallbackTicket++; diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp index a6a971282e..84965ef6da 100644 --- a/libs/gui/ConsumerBase.cpp +++ b/libs/gui/ConsumerBase.cpp @@ -235,6 +235,16 @@ status_t ConsumerBase::setDefaultBufferDataSpace( return mConsumer->setDefaultBufferDataSpace(defaultDataSpace); } +status_t ConsumerBase::getOccupancyHistory(bool forceFlush, + std::vector* outHistory) { + Mutex::Autolock _l(mMutex); + if (mAbandoned) { + CB_LOGE("getOccupancyHistory: ConsumerBase is abandoned!"); + return NO_INIT; + } + return mConsumer->getOccupancyHistory(forceFlush, outHistory); +} + void ConsumerBase::dump(String8& result) const { dump(result, ""); } diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp index cb1ad3525e..7c4379f41e 100644 --- a/libs/gui/IGraphicBufferConsumer.cpp +++ b/libs/gui/IGraphicBufferConsumer.cpp @@ -51,6 +51,7 @@ enum { SET_CONSUMER_USAGE_BITS, SET_TRANSFORM_HINT, GET_SIDEBAND_STREAM, + GET_OCCUPANCY_HISTORY, DUMP, }; @@ -260,6 +261,31 @@ public: return stream; } + virtual status_t getOccupancyHistory(bool forceFlush, + std::vector* outHistory) { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); + status_t error = data.writeBool(forceFlush); + if (error != NO_ERROR) { + return error; + } + error = remote()->transact(GET_OCCUPANCY_HISTORY, data, + &reply); + if (error != NO_ERROR) { + return error; + } + error = reply.readParcelableVector(outHistory); + if (error != NO_ERROR) { + return error; + } + status_t result = NO_ERROR; + error = reply.readInt32(&result); + if (error != NO_ERROR) { + return error; + } + return result; + } + virtual void dump(String8& result, const char* prefix) const { Parcel data, reply; data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); @@ -409,6 +435,25 @@ status_t BnGraphicBufferConsumer::onTransact( } return NO_ERROR; } + case GET_OCCUPANCY_HISTORY: { + CHECK_INTERFACE(IGraphicBufferConsumer, data, reply); + bool forceFlush = false; + status_t error = data.readBool(&forceFlush); + if (error != NO_ERROR) { + return error; + } + std::vector history; + status_t result = getOccupancyHistory(forceFlush, &history); + error = reply->writeParcelableVector(history); + if (error != NO_ERROR) { + return error; + } + error = reply->writeInt32(result); + if (error != NO_ERROR) { + return error; + } + return NO_ERROR; + } case DUMP: { CHECK_INTERFACE(IGraphicBufferConsumer, data, reply); String8 result = data.readString8(); diff --git a/libs/gui/OccupancyTracker.cpp b/libs/gui/OccupancyTracker.cpp new file mode 100644 index 0000000000..9687aaf744 --- /dev/null +++ b/libs/gui/OccupancyTracker.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2016 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 "OccupancyTracker" + +#include +#include +#include +#include + +#include + +namespace android { + +status_t OccupancyTracker::Segment::writeToParcel(Parcel* parcel) const { + status_t result = parcel->writeInt64(totalTime); + if (result != OK) { + return result; + } + result = parcel->writeUint64(static_cast(numFrames)); + if (result != OK) { + return result; + } + result = parcel->writeFloat(occupancyAverage); + if (result != OK) { + return result; + } + return parcel->writeBool(usedThirdBuffer); +} + +status_t OccupancyTracker::Segment::readFromParcel(const Parcel* parcel) { + status_t result = parcel->readInt64(&totalTime); + if (result != OK) { + return result; + } + uint64_t uintNumFrames = 0; + result = parcel->readUint64(&uintNumFrames); + if (result != OK) { + return result; + } + numFrames = static_cast(uintNumFrames); + result = parcel->readFloat(&occupancyAverage); + if (result != OK) { + return result; + } + return parcel->readBool(&usedThirdBuffer); +} + +void OccupancyTracker::registerOccupancyChange(size_t occupancy) { + ATRACE_CALL(); + nsecs_t now = systemTime(); + nsecs_t delta = now - mLastOccupancyChangeTime; + if (delta > NEW_SEGMENT_DELAY) { + recordPendingSegment(); + } else { + mPendingSegment.totalTime += delta; + if (mPendingSegment.mOccupancyTimes.count(mLastOccupancy)) { + mPendingSegment.mOccupancyTimes[mLastOccupancy] += delta; + } else { + mPendingSegment.mOccupancyTimes[mLastOccupancy] = delta; + } + } + if (occupancy > mLastOccupancy) { + ++mPendingSegment.numFrames; + } + mLastOccupancyChangeTime = now; + mLastOccupancy = occupancy; +} + +std::vector OccupancyTracker::getSegmentHistory( + bool forceFlush) { + if (forceFlush) { + recordPendingSegment(); + } + std::vector segments(mSegmentHistory.cbegin(), + mSegmentHistory.cend()); + mSegmentHistory.clear(); + return segments; +} + +void OccupancyTracker::recordPendingSegment() { + // Only record longer segments to get a better measurement of actual double- + // vs. triple-buffered time + if (mPendingSegment.numFrames > LONG_SEGMENT_THRESHOLD) { + float occupancyAverage = 0.0f; + bool usedThirdBuffer = false; + for (const auto& timePair : mPendingSegment.mOccupancyTimes) { + size_t occupancy = timePair.first; + float timeRatio = static_cast(timePair.second) / + mPendingSegment.totalTime; + occupancyAverage += timeRatio * occupancy; + usedThirdBuffer = usedThirdBuffer || (occupancy > 1); + } + mSegmentHistory.push_front({mPendingSegment.totalTime, + mPendingSegment.numFrames, occupancyAverage, usedThirdBuffer}); + if (mSegmentHistory.size() > MAX_HISTORY_SIZE) { + mSegmentHistory.pop_back(); + } + } + mPendingSegment.clear(); +} + +} // namespace android diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index 85d63b4ba2..210ce8c18e 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -34,6 +34,10 @@ #include +#include + +using namespace std::chrono_literals; + namespace android { class BufferQueueTest : public ::testing::Test { @@ -850,4 +854,140 @@ TEST_F(BufferQueueTest, CanRetrieveLastQueuedBuffer) { returnedBuffer->getNativeBuffer()->handle); } +TEST_F(BufferQueueTest, TestOccupancyHistory) { + createBufferQueue(); + sp dc(new DummyConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false)); + IGraphicBufferProducer::QueueBufferOutput output; + ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener, + NATIVE_WINDOW_API_CPU, false, &output)); + + int slot = BufferQueue::INVALID_BUFFER_SLOT; + sp fence = Fence::NO_FENCE; + sp buffer = nullptr; + IGraphicBufferProducer::QueueBufferInput input(0ull, true, + HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT, + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE); + BufferItem item{}; + + // Preallocate, dequeue, request, and cancel 3 buffers so we don't get + // BUFFER_NEEDS_REALLOCATION below + int slots[3] = {}; + mProducer->setMaxDequeuedBufferCount(3); + for (size_t i = 0; i < 3; ++i) { + status_t result = mProducer->dequeueBuffer(&slots[i], &fence, + 0, 0, 0, 0); + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result); + ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer)); + } + for (size_t i = 0; i < 3; ++i) { + ASSERT_EQ(OK, mProducer->cancelBuffer(slots[i], Fence::NO_FENCE)); + } + + // Create 3 segments + + // The first segment is a two-buffer segment, so we only put one buffer into + // the queue at a time + for (size_t i = 0; i < 5; ++i) { + ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + std::this_thread::sleep_for(16ms); + } + + // Sleep between segments + std::this_thread::sleep_for(500ms); + + // The second segment is a double-buffer segment. It starts the same as the + // two-buffer segment, but then at the end, we put two buffers in the queue + // at the same time before draining it. + for (size_t i = 0; i < 5; ++i) { + ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + std::this_thread::sleep_for(16ms); + } + ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + std::this_thread::sleep_for(16ms); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + + // Sleep between segments + std::this_thread::sleep_for(500ms); + + // The third segment is a triple-buffer segment, so the queue is switching + // between one buffer and two buffers deep. + ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + for (size_t i = 0; i < 5; ++i) { + ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + std::this_thread::sleep_for(16ms); + } + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + + // Now we read the segments + std::vector history; + ASSERT_EQ(OK, mConsumer->getOccupancyHistory(false, &history)); + + // Since we didn't force a flush, we should only get the first two segments + // (since the third segment hasn't been closed out by the appearance of a + // new segment yet) + ASSERT_EQ(2u, history.size()); + + // The first segment (which will be history[1], since the newest segment + // should be at the front of the vector) should be a two-buffer segment, + // which implies that the occupancy average should be between 0 and 1, and + // usedThirdBuffer should be false + const auto& firstSegment = history[1]; + ASSERT_EQ(5u, firstSegment.numFrames); + ASSERT_LT(0, firstSegment.occupancyAverage); + ASSERT_GT(1, firstSegment.occupancyAverage); + ASSERT_EQ(false, firstSegment.usedThirdBuffer); + + // The second segment should be a double-buffered segment, which implies that + // the occupancy average should be between 0 and 1, but usedThirdBuffer + // should be true + const auto& secondSegment = history[0]; + ASSERT_EQ(7u, secondSegment.numFrames); + ASSERT_LT(0, secondSegment.occupancyAverage); + ASSERT_GT(1, secondSegment.occupancyAverage); + ASSERT_EQ(true, secondSegment.usedThirdBuffer); + + // If we read the segments again without flushing, we shouldn't get any new + // segments + ASSERT_EQ(OK, mConsumer->getOccupancyHistory(false, &history)); + ASSERT_EQ(0u, history.size()); + + // Read the segments again, this time forcing a flush so we get the third + // segment + ASSERT_EQ(OK, mConsumer->getOccupancyHistory(true, &history)); + ASSERT_EQ(1u, history.size()); + + // This segment should be a triple-buffered segment, which implies that the + // occupancy average should be between 1 and 2, and usedThirdBuffer should + // be true + const auto& thirdSegment = history[0]; + ASSERT_EQ(6u, thirdSegment.numFrames); + ASSERT_LT(1, thirdSegment.occupancyAverage); + ASSERT_GT(2, thirdSegment.occupancyAverage); + ASSERT_EQ(true, thirdSegment.usedThirdBuffer); +} + } // namespace android diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index ed320cb3d0..59a604a4e3 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1680,7 +1680,8 @@ bool Layer::onPreComposition() { return mQueuedFrames > 0 || mSidebandStreamChanged || mAutoRefresh; } -void Layer::onPostComposition() { +bool Layer::onPostComposition() { + bool frameLatencyNeeded = mFrameLatencyNeeded; if (mFrameLatencyNeeded) { nsecs_t desiredPresentTime = mSurfaceFlingerConsumer->getTimestamp(); mFrameTracker.setDesiredPresentTime(desiredPresentTime); @@ -1712,6 +1713,7 @@ void Layer::onPostComposition() { mFrameTracker.advanceFrame(); mFrameLatencyNeeded = false; } + return frameLatencyNeeded; } #ifdef USE_HWC2 @@ -2185,6 +2187,20 @@ void Layer::getFenceData(String8* outName, uint64_t* outFrameNumber, *outAcquireFence = mSurfaceFlingerConsumer->getCurrentFence(); *outPrevReleaseFence = mSurfaceFlingerConsumer->getPrevReleaseFence(); } + +std::vector Layer::getOccupancyHistory( + bool forceFlush) { + std::vector history; + status_t result = mSurfaceFlingerConsumer->getOccupancyHistory(forceFlush, + &history); + if (result != NO_ERROR) { + ALOGW("[%s] Failed to obtain occupancy history (%d)", mName.string(), + result); + return {}; + } + return history; +} + // --------------------------------------------------------------------------- Layer::LayerCleaner::LayerCleaner(const sp& flinger, diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 7d085a479a..79750a6d42 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -274,9 +274,10 @@ public: bool onPreComposition(); /* - * called after composition. + * called after composition. + * returns true if the layer latched a new buffer this frame. */ - void onPostComposition(); + bool onPostComposition(); #ifdef USE_HWC2 // If a buffer was replaced this frame, release the former buffer @@ -406,6 +407,8 @@ public: bool* outIsGlesComposition, nsecs_t* outPostedTime, sp* outAcquireFence, sp* outPrevReleaseFence) const; + std::vector getOccupancyHistory(bool forceFlush); + protected: // constant sp mFlinger; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 91815f314e..a730ac67d7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1049,7 +1049,11 @@ void SurfaceFlinger::postComposition(nsecs_t /*refreshStartTime*/) const LayerVector& layers(mDrawingState.layersSortedByZ); const size_t count = layers.size(); for (size_t i=0 ; ionPostComposition(); + bool frameLatched = layers[i]->onPostComposition(); + if (frameLatched) { + recordBufferingStats(layers[i]->getName().string(), + layers[i]->getOccupancyHistory(false)); + } } sp presentFence = mHwc->getRetireFence(HWC_DISPLAY_PRIMARY); @@ -1641,6 +1645,8 @@ void SurfaceFlinger::commitTransaction() if (!mLayersPendingRemoval.isEmpty()) { // Notify removed layers now that they can't be drawn from for (size_t i = 0; i < mLayersPendingRemoval.size(); i++) { + recordBufferingStats(mLayersPendingRemoval[i]->getName().string(), + mLayersPendingRemoval[i]->getOccupancyHistory(true)); mLayersPendingRemoval[i]->onRemoved(); } mLayersPendingRemoval.clear(); @@ -2720,6 +2726,59 @@ void SurfaceFlinger::dumpStaticScreenStats(String8& result) const NUM_BUCKETS - 1, bucketTimeSec, percent); } +void SurfaceFlinger::recordBufferingStats(const char* layerName, + std::vector&& history) { + Mutex::Autolock lock(mBufferingStatsMutex); + auto& stats = mBufferingStats[layerName]; + for (const auto& segment : history) { + if (!segment.usedThirdBuffer) { + stats.twoBufferTime += segment.totalTime; + } + if (segment.occupancyAverage < 1.0f) { + stats.doubleBufferedTime += segment.totalTime; + } else if (segment.occupancyAverage < 2.0f) { + stats.tripleBufferedTime += segment.totalTime; + } + ++stats.numSegments; + stats.totalTime += segment.totalTime; + } +} + +void SurfaceFlinger::dumpBufferingStats(String8& result) const { + result.append("Buffering stats:\n"); + result.append(" [Layer name] " + " \n"); + Mutex::Autolock lock(mBufferingStatsMutex); + typedef std::tuple BufferTuple; + std::map> sorted; + for (const auto& statsPair : mBufferingStats) { + const char* name = statsPair.first.c_str(); + const BufferingStats& stats = statsPair.second; + if (stats.numSegments == 0) { + continue; + } + float activeTime = ns2ms(stats.totalTime) / 1000.0f; + float twoBufferRatio = static_cast(stats.twoBufferTime) / + stats.totalTime; + float doubleBufferRatio = static_cast( + stats.doubleBufferedTime) / stats.totalTime; + float tripleBufferRatio = static_cast( + stats.tripleBufferedTime) / stats.totalTime; + sorted.insert({activeTime, {name, twoBufferRatio, + doubleBufferRatio, tripleBufferRatio}}); + } + for (const auto& sortedPair : sorted) { + float activeTime = sortedPair.first; + const BufferTuple& values = sortedPair.second; + result.appendFormat(" [%s] %.2f %.3f %.3f %.3f\n", + std::get<0>(values).c_str(), activeTime, + std::get<1>(values), std::get<2>(values), + std::get<3>(values)); + } + result.append("\n"); +} + + void SurfaceFlinger::dumpAllLocked(const Vector& args, size_t& index, String8& result) const { @@ -2773,6 +2832,8 @@ void SurfaceFlinger::dumpAllLocked(const Vector& args, size_t& index, dumpStaticScreenStats(result); result.append("\n"); + dumpBufferingStats(result); + /* * Dump the visible layer list */ diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 633e956d24..8263994859 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -42,6 +42,7 @@ #include #include +#include #include @@ -57,6 +58,9 @@ #include "DisplayHardware/HWComposer.h" #include "Effects/Daltonizer.h" +#include +#include + namespace android { // --------------------------------------------------------------------------- @@ -432,6 +436,10 @@ private: void dumpStaticScreenStats(String8& result) const; + void recordBufferingStats(const char* layerName, + std::vector&& history); + void dumpBufferingStats(String8& result) const; + /* ------------------------------------------------------------------------ * Attributes */ @@ -526,6 +534,29 @@ private: nsecs_t mFrameBuckets[NUM_BUCKETS]; nsecs_t mTotalTime; std::atomic mLastSwapTime; + + // Double- vs. triple-buffering stats + struct BufferingStats { + BufferingStats() + : numSegments(0), + totalTime(0), + twoBufferTime(0), + doubleBufferedTime(0), + tripleBufferedTime(0) {} + + size_t numSegments; + nsecs_t totalTime; + + // "Two buffer" means that a third buffer was never used, whereas + // "double-buffered" means that on average the segment only used two + // buffers (though it may have used a third for some part of the + // segment) + nsecs_t twoBufferTime; + nsecs_t doubleBufferedTime; + nsecs_t tripleBufferedTime; + }; + mutable Mutex mBufferingStatsMutex; + std::unordered_map mBufferingStats; }; }; // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp index de46dfa0ff..ef4861d6a0 100644 --- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp +++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp @@ -1042,7 +1042,11 @@ void SurfaceFlinger::postComposition(nsecs_t /*refreshStartTime*/) const LayerVector& layers(mDrawingState.layersSortedByZ); const size_t count = layers.size(); for (size_t i=0 ; ionPostComposition(); + bool frameLatched = layers[i]->onPostComposition(); + if (frameLatched) { + recordBufferingStats(layers[i]->getName().string(), + layers[i]->getOccupancyHistory(false)); + } } const HWComposer& hwc = getHwComposer(); @@ -1670,6 +1674,8 @@ void SurfaceFlinger::commitTransaction() if (!mLayersPendingRemoval.isEmpty()) { // Notify removed layers now that they can't be drawn from for (size_t i = 0; i < mLayersPendingRemoval.size(); i++) { + recordBufferingStats(mLayersPendingRemoval[i]->getName().string(), + mLayersPendingRemoval[i]->getOccupancyHistory(true)); mLayersPendingRemoval[i]->onRemoved(); } mLayersPendingRemoval.clear(); @@ -2745,6 +2751,58 @@ void SurfaceFlinger::dumpStaticScreenStats(String8& result) const NUM_BUCKETS - 1, bucketTimeSec, percent); } +void SurfaceFlinger::recordBufferingStats(const char* layerName, + std::vector&& history) { + Mutex::Autolock lock(mBufferingStatsMutex); + auto& stats = mBufferingStats[layerName]; + for (const auto& segment : history) { + if (!segment.usedThirdBuffer) { + stats.twoBufferTime += segment.totalTime; + } + if (segment.occupancyAverage < 1.0f) { + stats.doubleBufferedTime += segment.totalTime; + } else if (segment.occupancyAverage < 2.0f) { + stats.tripleBufferedTime += segment.totalTime; + } + ++stats.numSegments; + stats.totalTime += segment.totalTime; + } +} + +void SurfaceFlinger::dumpBufferingStats(String8& result) const { + result.append("Buffering stats:\n"); + result.append(" [Layer name] " + " \n"); + Mutex::Autolock lock(mBufferingStatsMutex); + typedef std::tuple BufferTuple; + std::map> sorted; + for (const auto& statsPair : mBufferingStats) { + const char* name = statsPair.first.c_str(); + const BufferingStats& stats = statsPair.second; + if (stats.numSegments == 0) { + continue; + } + float activeTime = ns2ms(stats.totalTime) / 1000.0f; + float twoBufferRatio = static_cast(stats.twoBufferTime) / + stats.totalTime; + float doubleBufferRatio = static_cast( + stats.doubleBufferedTime) / stats.totalTime; + float tripleBufferRatio = static_cast( + stats.tripleBufferedTime) / stats.totalTime; + sorted.insert({activeTime, {name, twoBufferRatio, + doubleBufferRatio, tripleBufferRatio}}); + } + for (const auto& sortedPair : sorted) { + float activeTime = sortedPair.first; + const BufferTuple& values = sortedPair.second; + result.appendFormat(" [%s] %.2f %.3f %.3f %.3f\n", + std::get<0>(values).c_str(), activeTime, + std::get<1>(values), std::get<2>(values), + std::get<3>(values)); + } + result.append("\n"); +} + void SurfaceFlinger::dumpAllLocked(const Vector& args, size_t& index, String8& result) const { @@ -2796,6 +2854,8 @@ void SurfaceFlinger::dumpAllLocked(const Vector& args, size_t& index, dumpStaticScreenStats(result); result.append("\n"); + dumpBufferingStats(result); + /* * Dump the visible layer list */