// Retrieve the sideband buffer stream, if any.
virtual sp<NativeHandle> getSidebandStream() const;
+ // See IGraphicBufferConsumer::getOccupancyHistory
+ virtual status_t getOccupancyHistory(bool forceFlush,
+ std::vector<OccupancyTracker::Segment>* outHistory) override;
+
// dump our state in a String
virtual void dump(String8& result, const char* prefix) const;
#include <gui/BufferItem.h>
#include <gui/BufferQueueDefs.h>
#include <gui/BufferSlot.h>
+#include <gui/OccupancyTracker.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
// The slot of the last queued buffer
int mLastQueuedSlot;
+ OccupancyTracker mOccupancyTracker;
+
}; // class BufferQueueCore
} // namespace android
// See IGraphicBufferConsumer::setDefaultBufferDataSpace
status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace);
+ // See IGraphicBufferConsumer::getOccupancyHistory
+ status_t getOccupancyHistory(bool forceFlush,
+ std::vector<OccupancyTracker::Segment>* outHistory);
+
private:
ConsumerBase(const ConsumerBase&);
void operator=(const ConsumerBase&);
#include <binder/IInterface.h>
#include <ui/PixelFormat.h>
#include <ui/Rect.h>
+#include <gui/OccupancyTracker.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
// Retrieve the sideband buffer stream, if any.
virtual sp<NativeHandle> 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<OccupancyTracker::Segment>* outHistory) = 0;
+
// dump state into a string
virtual void dump(String8& result, const char* prefix) const = 0;
--- /dev/null
+/*
+ * 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 <binder/Parcelable.h>
+
+#include <utils/Timers.h>
+
+#include <deque>
+#include <unordered_map>
+
+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<Segment> 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<size_t, nsecs_t> mOccupancyTimes;
+ };
+
+ void recordPendingSegment();
+
+ PendingSegment mPendingSegment;
+ std::deque<Segment> mSegmentHistory;
+
+ size_t mLastOccupancy;
+ nsecs_t mLastOccupancyChangeTime;
+
+}; // class OccupancyTracker
+
+} // namespace android
+
+#endif
ISurfaceComposer.cpp \
ISurfaceComposerClient.cpp \
LayerState.cpp \
+ OccupancyTracker.cpp \
Sensor.cpp \
SensorEventQueue.cpp \
SensorManager.cpp \
mCore->mDequeueCondition.broadcast();
ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size());
+ mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
VALIDATE_CONSISTENCY();
}
return mCore->mSidebandStream;
}
+status_t BufferQueueConsumer::getOccupancyHistory(bool forceFlush,
+ std::vector<OccupancyTracker::Segment>* 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();
static_cast<uint32_t>(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++;
return mConsumer->setDefaultBufferDataSpace(defaultDataSpace);
}
+status_t ConsumerBase::getOccupancyHistory(bool forceFlush,
+ std::vector<OccupancyTracker::Segment>* 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, "");
}
SET_CONSUMER_USAGE_BITS,
SET_TRANSFORM_HINT,
GET_SIDEBAND_STREAM,
+ GET_OCCUPANCY_HISTORY,
DUMP,
};
return stream;
}
+ virtual status_t getOccupancyHistory(bool forceFlush,
+ std::vector<OccupancyTracker::Segment>* 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());
}
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<OccupancyTracker::Segment> 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();
--- /dev/null
+/*
+ * 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 <gui/OccupancyTracker.h>
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+#include <utils/Trace.h>
+
+#include <inttypes.h>
+
+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<uint64_t>(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<size_t>(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::Segment> OccupancyTracker::getSegmentHistory(
+ bool forceFlush) {
+ if (forceFlush) {
+ recordPendingSegment();
+ }
+ std::vector<Segment> 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<float>(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
#include <gtest/gtest.h>
+#include <thread>
+
+using namespace std::chrono_literals;
+
namespace android {
class BufferQueueTest : public ::testing::Test {
returnedBuffer->getNativeBuffer()->handle);
}
+TEST_F(BufferQueueTest, TestOccupancyHistory) {
+ createBufferQueue();
+ sp<DummyConsumer> 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 = Fence::NO_FENCE;
+ sp<GraphicBuffer> 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<OccupancyTracker::Segment> 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
return mQueuedFrames > 0 || mSidebandStreamChanged || mAutoRefresh;
}
-void Layer::onPostComposition() {
+bool Layer::onPostComposition() {
+ bool frameLatencyNeeded = mFrameLatencyNeeded;
if (mFrameLatencyNeeded) {
nsecs_t desiredPresentTime = mSurfaceFlingerConsumer->getTimestamp();
mFrameTracker.setDesiredPresentTime(desiredPresentTime);
mFrameTracker.advanceFrame();
mFrameLatencyNeeded = false;
}
+ return frameLatencyNeeded;
}
#ifdef USE_HWC2
*outAcquireFence = mSurfaceFlingerConsumer->getCurrentFence();
*outPrevReleaseFence = mSurfaceFlingerConsumer->getPrevReleaseFence();
}
+
+std::vector<OccupancyTracker::Segment> Layer::getOccupancyHistory(
+ bool forceFlush) {
+ std::vector<OccupancyTracker::Segment> 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<SurfaceFlinger>& flinger,
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
bool* outIsGlesComposition, nsecs_t* outPostedTime,
sp<Fence>* outAcquireFence, sp<Fence>* outPrevReleaseFence) const;
+ std::vector<OccupancyTracker::Segment> getOccupancyHistory(bool forceFlush);
+
protected:
// constant
sp<SurfaceFlinger> mFlinger;
const LayerVector& layers(mDrawingState.layersSortedByZ);
const size_t count = layers.size();
for (size_t i=0 ; i<count ; i++) {
- layers[i]->onPostComposition();
+ bool frameLatched = layers[i]->onPostComposition();
+ if (frameLatched) {
+ recordBufferingStats(layers[i]->getName().string(),
+ layers[i]->getOccupancyHistory(false));
+ }
}
sp<Fence> presentFence = mHwc->getRetireFence(HWC_DISPLAY_PRIMARY);
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();
NUM_BUCKETS - 1, bucketTimeSec, percent);
}
+void SurfaceFlinger::recordBufferingStats(const char* layerName,
+ std::vector<OccupancyTracker::Segment>&& 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] <Active time> <Two buffer> "
+ "<Double buffered> <Triple buffered>\n");
+ Mutex::Autolock lock(mBufferingStatsMutex);
+ typedef std::tuple<std::string, float, float, float> BufferTuple;
+ std::map<float, BufferTuple, std::greater<float>> 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<float>(stats.twoBufferTime) /
+ stats.totalTime;
+ float doubleBufferRatio = static_cast<float>(
+ stats.doubleBufferedTime) / stats.totalTime;
+ float tripleBufferRatio = static_cast<float>(
+ 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<String16>& args, size_t& index,
String8& result) const
{
dumpStaticScreenStats(result);
result.append("\n");
+ dumpBufferingStats(result);
+
/*
* Dump the visible layer list
*/
#include <gui/ISurfaceComposer.h>
#include <gui/ISurfaceComposerClient.h>
+#include <gui/OccupancyTracker.h>
#include <hardware/hwcomposer_defs.h>
#include "DisplayHardware/HWComposer.h"
#include "Effects/Daltonizer.h"
+#include <map>
+#include <string>
+
namespace android {
// ---------------------------------------------------------------------------
void dumpStaticScreenStats(String8& result) const;
+ void recordBufferingStats(const char* layerName,
+ std::vector<OccupancyTracker::Segment>&& history);
+ void dumpBufferingStats(String8& result) const;
+
/* ------------------------------------------------------------------------
* Attributes
*/
nsecs_t mFrameBuckets[NUM_BUCKETS];
nsecs_t mTotalTime;
std::atomic<nsecs_t> 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<std::string, BufferingStats> mBufferingStats;
};
}; // namespace android
const LayerVector& layers(mDrawingState.layersSortedByZ);
const size_t count = layers.size();
for (size_t i=0 ; i<count ; i++) {
- layers[i]->onPostComposition();
+ bool frameLatched = layers[i]->onPostComposition();
+ if (frameLatched) {
+ recordBufferingStats(layers[i]->getName().string(),
+ layers[i]->getOccupancyHistory(false));
+ }
}
const HWComposer& hwc = getHwComposer();
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();
NUM_BUCKETS - 1, bucketTimeSec, percent);
}
+void SurfaceFlinger::recordBufferingStats(const char* layerName,
+ std::vector<OccupancyTracker::Segment>&& 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] <Active time> <Two buffer> "
+ "<Double buffered> <Triple buffered>\n");
+ Mutex::Autolock lock(mBufferingStatsMutex);
+ typedef std::tuple<std::string, float, float, float> BufferTuple;
+ std::map<float, BufferTuple, std::greater<float>> 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<float>(stats.twoBufferTime) /
+ stats.totalTime;
+ float doubleBufferRatio = static_cast<float>(
+ stats.doubleBufferedTime) / stats.totalTime;
+ float tripleBufferRatio = static_cast<float>(
+ 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<String16>& args, size_t& index,
String8& result) const
{
dumpStaticScreenStats(result);
result.append("\n");
+ dumpBufferingStats(result);
+
/*
* Dump the visible layer list
*/