blob: bd87482dc6683ac7b35181972da9e653895c75e2 [file] [log] [blame]
/*
* Copyright 2020 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 "FrameTimeline"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "FrameTimeline.h"
#include <android-base/stringprintf.h>
#include <utils/Log.h>
#include <utils/Trace.h>
#include <chrono>
#include <cinttypes>
#include <numeric>
namespace android::frametimeline::impl {
using base::StringAppendF;
void dumpTable(std::string& result, TimelineItem predictions, TimelineItem actuals,
const std::string& indent, PredictionState predictionState, nsecs_t baseTime) {
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "\t\t");
StringAppendF(&result, " Start time\t\t|");
StringAppendF(&result, " End time\t\t|");
StringAppendF(&result, " Present time\n");
if (predictionState == PredictionState::Valid) {
// Dump the Predictions only if they are valid
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Expected\t|");
std::chrono::nanoseconds startTime(predictions.startTime - baseTime);
std::chrono::nanoseconds endTime(predictions.endTime - baseTime);
std::chrono::nanoseconds presentTime(predictions.presentTime - baseTime);
StringAppendF(&result, "\t%10.2f\t|\t%10.2f\t|\t%10.2f\n",
std::chrono::duration<double, std::milli>(startTime).count(),
std::chrono::duration<double, std::milli>(endTime).count(),
std::chrono::duration<double, std::milli>(presentTime).count());
}
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Actual \t|");
if (actuals.startTime == 0) {
StringAppendF(&result, "\t\tN/A\t|");
} else {
std::chrono::nanoseconds startTime(std::max<nsecs_t>(0, actuals.startTime - baseTime));
StringAppendF(&result, "\t%10.2f\t|",
std::chrono::duration<double, std::milli>(startTime).count());
}
if (actuals.endTime == 0) {
StringAppendF(&result, "\t\tN/A\t|");
} else {
std::chrono::nanoseconds endTime(actuals.endTime - baseTime);
StringAppendF(&result, "\t%10.2f\t|",
std::chrono::duration<double, std::milli>(endTime).count());
}
if (actuals.presentTime == 0) {
StringAppendF(&result, "\t\tN/A\n");
} else {
std::chrono::nanoseconds presentTime(std::max<nsecs_t>(0, actuals.presentTime - baseTime));
StringAppendF(&result, "\t%10.2f\n",
std::chrono::duration<double, std::milli>(presentTime).count());
}
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "----------------------");
StringAppendF(&result, "----------------------");
StringAppendF(&result, "----------------------");
StringAppendF(&result, "----------------------\n");
}
std::string toString(PredictionState predictionState) {
switch (predictionState) {
case PredictionState::Valid:
return "Valid";
case PredictionState::Expired:
return "Expired";
case PredictionState::None:
default:
return "None";
}
}
std::string toString(TimeStats::JankType jankType) {
switch (jankType) {
case TimeStats::JankType::None:
return "None";
case TimeStats::JankType::Display:
return "Composer/Display - outside SF and App";
case TimeStats::JankType::SurfaceFlingerDeadlineMissed:
return "SurfaceFlinger Deadline Missed";
case TimeStats::JankType::AppDeadlineMissed:
return "App Deadline Missed";
case TimeStats::JankType::PredictionExpired:
return "Prediction Expired";
case TimeStats::JankType::SurfaceFlingerEarlyLatch:
return "SurfaceFlinger Early Latch";
default:
return "Unclassified";
}
}
std::string jankMetadataBitmaskToString(int32_t jankMetadata) {
std::vector<std::string> jankInfo;
if (jankMetadata & EarlyStart) {
jankInfo.emplace_back("Early Start");
} else if (jankMetadata & LateStart) {
jankInfo.emplace_back("Late Start");
}
if (jankMetadata & EarlyFinish) {
jankInfo.emplace_back("Early Finish");
} else if (jankMetadata & LateFinish) {
jankInfo.emplace_back("Late Finish");
}
if (jankMetadata & EarlyPresent) {
jankInfo.emplace_back("Early Present");
} else if (jankMetadata & LatePresent) {
jankInfo.emplace_back("Late Present");
}
// TODO(b/169876734): add GPU composition metadata here
if (jankInfo.empty()) {
return "None";
}
return std::accumulate(jankInfo.begin(), jankInfo.end(), std::string(),
[](const std::string& l, const std::string& r) {
return l.empty() ? r : l + ", " + r;
});
}
int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
ATRACE_CALL();
std::lock_guard<std::mutex> lock(mMutex);
const int64_t assignedToken = mCurrentToken++;
mPredictions[assignedToken] = predictions;
mTokens.emplace_back(std::make_pair(assignedToken, systemTime()));
flushTokens(systemTime());
return assignedToken;
}
std::optional<TimelineItem> TokenManager::getPredictionsForToken(int64_t token) {
std::lock_guard<std::mutex> lock(mMutex);
flushTokens(systemTime());
auto predictionsIterator = mPredictions.find(token);
if (predictionsIterator != mPredictions.end()) {
return predictionsIterator->second;
}
return {};
}
void TokenManager::flushTokens(nsecs_t flushTime) {
for (size_t i = 0; i < mTokens.size(); i++) {
if (flushTime - mTokens[i].second >= kMaxRetentionTime) {
mPredictions.erase(mTokens[i].first);
mTokens.erase(mTokens.begin() + static_cast<int>(i));
--i;
} else {
// Tokens are ordered by time. If i'th token is within the retention time, then the
// i+1'th token will also be within retention time.
break;
}
}
}
SurfaceFrame::SurfaceFrame(pid_t ownerPid, uid_t ownerUid, std::string layerName,
std::string debugName, PredictionState predictionState,
frametimeline::TimelineItem&& predictions)
: mOwnerPid(ownerPid),
mOwnerUid(ownerUid),
mLayerName(std::move(layerName)),
mDebugName(std::move(debugName)),
mPresentState(PresentState::Unknown),
mPredictionState(predictionState),
mPredictions(predictions),
mActuals({0, 0, 0}),
mActualQueueTime(0),
mJankType(TimeStats::JankType::None),
mJankMetadata(0) {}
void SurfaceFrame::setPresentState(PresentState state) {
std::lock_guard<std::mutex> lock(mMutex);
mPresentState = state;
}
SurfaceFrame::PresentState SurfaceFrame::getPresentState() const {
std::lock_guard<std::mutex> lock(mMutex);
return mPresentState;
}
TimelineItem SurfaceFrame::getActuals() const {
std::lock_guard<std::mutex> lock(mMutex);
return mActuals;
}
nsecs_t SurfaceFrame::getActualQueueTime() const {
std::lock_guard<std::mutex> lock(mMutex);
return mActualQueueTime;
}
void SurfaceFrame::setActualStartTime(nsecs_t actualStartTime) {
std::lock_guard<std::mutex> lock(mMutex);
mActuals.startTime = actualStartTime;
}
void SurfaceFrame::setActualQueueTime(nsecs_t actualQueueTime) {
std::lock_guard<std::mutex> lock(mMutex);
mActualQueueTime = actualQueueTime;
}
void SurfaceFrame::setAcquireFenceTime(nsecs_t acquireFenceTime) {
std::lock_guard<std::mutex> lock(mMutex);
mActuals.endTime = std::max(acquireFenceTime, mActualQueueTime);
}
void SurfaceFrame::setActualPresentTime(nsecs_t presentTime) {
std::lock_guard<std::mutex> lock(mMutex);
mActuals.presentTime = presentTime;
}
void SurfaceFrame::setJankInfo(TimeStats::JankType jankType, int32_t jankMetadata) {
std::lock_guard<std::mutex> lock(mMutex);
mJankType = jankType;
mJankMetadata = jankMetadata;
}
TimeStats::JankType SurfaceFrame::getJankType() const {
std::lock_guard<std::mutex> lock(mMutex);
return mJankType;
}
nsecs_t SurfaceFrame::getBaseTime() const {
std::lock_guard<std::mutex> lock(mMutex);
nsecs_t baseTime = std::numeric_limits<nsecs_t>::max();
if (mPredictionState == PredictionState::Valid) {
baseTime = std::min(baseTime, mPredictions.startTime);
}
if (mActuals.startTime != 0) {
baseTime = std::min(baseTime, mActuals.startTime);
}
baseTime = std::min(baseTime, mActuals.endTime);
return baseTime;
}
std::string presentStateToString(SurfaceFrame::PresentState presentState) {
using PresentState = SurfaceFrame::PresentState;
switch (presentState) {
case PresentState::Presented:
return "Presented";
case PresentState::Dropped:
return "Dropped";
case PresentState::Unknown:
default:
return "Unknown";
}
}
void SurfaceFrame::dump(std::string& result, const std::string& indent, nsecs_t baseTime) {
std::lock_guard<std::mutex> lock(mMutex);
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Layer - %s", mDebugName.c_str());
if (mJankType != TimeStats::JankType::None) {
// Easily identify a janky Surface Frame in the dump
StringAppendF(&result, " [*] ");
}
StringAppendF(&result, "\n");
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Owner Pid : %d\n", mOwnerPid);
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Present State : %s\n", presentStateToString(mPresentState).c_str());
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Prediction State : %s\n", toString(mPredictionState).c_str());
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Jank Type : %s\n", toString(mJankType).c_str());
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Jank Metadata: %s\n",
jankMetadataBitmaskToString(mJankMetadata).c_str());
dumpTable(result, mPredictions, mActuals, indent, mPredictionState, baseTime);
}
FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
: mCurrentDisplayFrame(std::make_shared<DisplayFrame>()),
mMaxDisplayFrames(kDefaultMaxDisplayFrames),
mTimeStats(std::move(timeStats)) {}
FrameTimeline::DisplayFrame::DisplayFrame()
: surfaceFlingerPredictions(TimelineItem()),
surfaceFlingerActuals(TimelineItem()),
predictionState(PredictionState::None),
jankType(TimeStats::JankType::None),
jankMetadata(0) {
this->surfaceFrames.reserve(kNumSurfaceFramesInitial);
}
std::unique_ptr<android::frametimeline::SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
std::optional<int64_t> token) {
ATRACE_CALL();
if (!token) {
return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
std::move(debugName), PredictionState::None,
TimelineItem());
}
std::optional<TimelineItem> predictions = mTokenManager.getPredictionsForToken(*token);
if (predictions) {
return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
std::move(debugName), PredictionState::Valid,
std::move(*predictions));
}
return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
std::move(debugName), PredictionState::Expired,
TimelineItem());
}
void FrameTimeline::addSurfaceFrame(
std::unique_ptr<android::frametimeline::SurfaceFrame> surfaceFrame,
SurfaceFrame::PresentState state) {
ATRACE_CALL();
surfaceFrame->setPresentState(state);
std::unique_ptr<impl::SurfaceFrame> implSurfaceFrame(
static_cast<impl::SurfaceFrame*>(surfaceFrame.release()));
std::lock_guard<std::mutex> lock(mMutex);
mCurrentDisplayFrame->surfaceFrames.push_back(std::move(implSurfaceFrame));
}
void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime) {
ATRACE_CALL();
const std::optional<TimelineItem> prediction = mTokenManager.getPredictionsForToken(token);
std::lock_guard<std::mutex> lock(mMutex);
if (!prediction) {
mCurrentDisplayFrame->predictionState = PredictionState::Expired;
} else {
mCurrentDisplayFrame->surfaceFlingerPredictions = *prediction;
mCurrentDisplayFrame->predictionState = PredictionState::Valid;
}
mCurrentDisplayFrame->surfaceFlingerActuals.startTime = wakeUpTime;
}
void FrameTimeline::setSfPresent(nsecs_t sfPresentTime,
const std::shared_ptr<FenceTime>& presentFence) {
ATRACE_CALL();
std::lock_guard<std::mutex> lock(mMutex);
mCurrentDisplayFrame->surfaceFlingerActuals.endTime = sfPresentTime;
mPendingPresentFences.emplace_back(std::make_pair(presentFence, mCurrentDisplayFrame));
flushPendingPresentFences();
finalizeCurrentDisplayFrame();
}
void FrameTimeline::flushPendingPresentFences() {
for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
const auto& pendingPresentFence = mPendingPresentFences[i];
nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
if (pendingPresentFence.first && pendingPresentFence.first->isValid()) {
signalTime = pendingPresentFence.first->getSignalTime();
if (signalTime == Fence::SIGNAL_TIME_PENDING) {
continue;
}
}
if (signalTime != Fence::SIGNAL_TIME_INVALID) {
int32_t totalJankReasons = TimeStats::JankType::None;
auto& displayFrame = pendingPresentFence.second;
displayFrame->surfaceFlingerActuals.presentTime = signalTime;
// Jank Analysis for DisplayFrame
const auto& sfActuals = displayFrame->surfaceFlingerActuals;
const auto& sfPredictions = displayFrame->surfaceFlingerPredictions;
if (std::abs(sfActuals.presentTime - sfPredictions.presentTime) > kPresentThreshold) {
displayFrame->jankMetadata |= sfActuals.presentTime > sfPredictions.presentTime
? LatePresent
: EarlyPresent;
}
if (std::abs(sfActuals.endTime - sfPredictions.endTime) > kDeadlineThreshold) {
if (sfActuals.endTime > sfPredictions.endTime) {
displayFrame->jankMetadata |= LateFinish;
} else {
displayFrame->jankMetadata |= EarlyFinish;
}
if ((displayFrame->jankMetadata & EarlyFinish) &&
(displayFrame->jankMetadata & EarlyPresent)) {
displayFrame->jankType = TimeStats::JankType::SurfaceFlingerEarlyLatch;
} else if ((displayFrame->jankMetadata & LateFinish) &&
(displayFrame->jankMetadata & LatePresent)) {
displayFrame->jankType = TimeStats::JankType::SurfaceFlingerDeadlineMissed;
} else if (displayFrame->jankMetadata & EarlyPresent ||
displayFrame->jankMetadata & LatePresent) {
// Cases where SF finished early but frame was presented late and vice versa
displayFrame->jankType = TimeStats::JankType::Display;
}
}
if (std::abs(sfActuals.startTime - sfPredictions.startTime) > kSFStartThreshold) {
displayFrame->jankMetadata |=
sfActuals.startTime > sfPredictions.startTime ? LateStart : EarlyStart;
}
totalJankReasons |= displayFrame->jankType;
for (auto& surfaceFrame : displayFrame->surfaceFrames) {
if (surfaceFrame->getPresentState() == SurfaceFrame::PresentState::Presented) {
// Only presented SurfaceFrames need to be updated
surfaceFrame->setActualPresentTime(signalTime);
// Jank Analysis for SurfaceFrame
const auto& predictionState = surfaceFrame->getPredictionState();
if (predictionState == PredictionState::Expired) {
// Jank analysis cannot be done on apps that don't use predictions
surfaceFrame->setJankInfo(TimeStats::JankType::PredictionExpired, 0);
continue;
} else if (predictionState == PredictionState::Valid) {
const auto& actuals = surfaceFrame->getActuals();
const auto& predictions = surfaceFrame->getPredictions();
int32_t jankMetadata = 0;
TimeStats::JankType jankType = TimeStats::JankType::None;
if (std::abs(actuals.endTime - predictions.endTime) > kDeadlineThreshold) {
jankMetadata |= actuals.endTime > predictions.endTime ? LateFinish
: EarlyFinish;
}
if (std::abs(actuals.presentTime - predictions.presentTime) >
kPresentThreshold) {
jankMetadata |= actuals.presentTime > predictions.presentTime
? LatePresent
: EarlyPresent;
}
if (jankMetadata & EarlyPresent) {
jankType = TimeStats::JankType::SurfaceFlingerEarlyLatch;
} else if (jankMetadata & LatePresent) {
if (jankMetadata & EarlyFinish) {
// TODO(b/169890654): Classify this properly
jankType = TimeStats::JankType::Display;
} else {
jankType = TimeStats::JankType::AppDeadlineMissed;
}
}
totalJankReasons |= jankType;
mTimeStats->incrementJankyFrames(surfaceFrame->getOwnerUid(),
surfaceFrame->getName(),
jankType | displayFrame->jankType);
surfaceFrame->setJankInfo(jankType, jankMetadata);
}
}
}
mTimeStats->incrementJankyFrames(totalJankReasons);
}
mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
--i;
}
}
void FrameTimeline::finalizeCurrentDisplayFrame() {
while (mDisplayFrames.size() >= mMaxDisplayFrames) {
// We maintain only a fixed number of frames' data. Pop older frames
mDisplayFrames.pop_front();
}
mDisplayFrames.push_back(mCurrentDisplayFrame);
mCurrentDisplayFrame.reset();
mCurrentDisplayFrame = std::make_shared<DisplayFrame>();
}
nsecs_t FrameTimeline::findBaseTime(const std::shared_ptr<DisplayFrame>& displayFrame) {
nsecs_t baseTime = std::numeric_limits<nsecs_t>::max();
if (displayFrame->predictionState == PredictionState::Valid) {
baseTime = std::min(baseTime, displayFrame->surfaceFlingerPredictions.startTime);
}
baseTime = std::min(baseTime, displayFrame->surfaceFlingerActuals.startTime);
for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
nsecs_t surfaceFrameBaseTime = surfaceFrame->getBaseTime();
if (surfaceFrameBaseTime != 0) {
baseTime = std::min(baseTime, surfaceFrameBaseTime);
}
}
return baseTime;
}
void FrameTimeline::dumpDisplayFrame(std::string& result,
const std::shared_ptr<DisplayFrame>& displayFrame,
nsecs_t baseTime) {
if (displayFrame->jankType != TimeStats::JankType::None) {
// Easily identify a janky Display Frame in the dump
StringAppendF(&result, " [*] ");
}
StringAppendF(&result, "\n");
StringAppendF(&result, "Prediction State : %s\n",
toString(displayFrame->predictionState).c_str());
StringAppendF(&result, "Jank Type : %s\n", toString(displayFrame->jankType).c_str());
StringAppendF(&result, "Jank Metadata: %s\n",
jankMetadataBitmaskToString(displayFrame->jankMetadata).c_str());
dumpTable(result, displayFrame->surfaceFlingerPredictions, displayFrame->surfaceFlingerActuals,
"", displayFrame->predictionState, baseTime);
StringAppendF(&result, "\n");
std::string indent = " "; // 4 spaces
for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
surfaceFrame->dump(result, indent, baseTime);
}
StringAppendF(&result, "\n");
}
void FrameTimeline::dumpAll(std::string& result) {
std::lock_guard<std::mutex> lock(mMutex);
StringAppendF(&result, "Number of display frames : %d\n", (int)mDisplayFrames.size());
nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : findBaseTime(mDisplayFrames[0]);
for (size_t i = 0; i < mDisplayFrames.size(); i++) {
StringAppendF(&result, "Display Frame %d", static_cast<int>(i));
dumpDisplayFrame(result, mDisplayFrames[i], baseTime);
}
}
void FrameTimeline::dumpJank(std::string& result) {
std::lock_guard<std::mutex> lock(mMutex);
nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : findBaseTime(mDisplayFrames[0]);
for (size_t i = 0; i < mDisplayFrames.size(); i++) {
const auto& displayFrame = mDisplayFrames[i];
if (displayFrame->jankType == TimeStats::JankType::None) {
// Check if any Surface Frame has been janky
bool isJanky = false;
for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
if (surfaceFrame->getJankType() != TimeStats::JankType::None) {
isJanky = true;
break;
}
}
if (!isJanky) {
continue;
}
}
StringAppendF(&result, "Display Frame %d", static_cast<int>(i));
dumpDisplayFrame(result, displayFrame, baseTime);
}
}
void FrameTimeline::parseArgs(const Vector<String16>& args, std::string& result) {
ATRACE_CALL();
std::unordered_map<std::string, bool> argsMap;
for (size_t i = 0; i < args.size(); i++) {
argsMap[std::string(String8(args[i]).c_str())] = true;
}
if (argsMap.count("-jank")) {
dumpJank(result);
}
if (argsMap.count("-all")) {
dumpAll(result);
}
}
void FrameTimeline::setMaxDisplayFrames(uint32_t size) {
std::lock_guard<std::mutex> lock(mMutex);
// The size can either increase or decrease, clear everything, to be consistent
mDisplayFrames.clear();
mPendingPresentFences.clear();
mMaxDisplayFrames = size;
}
void FrameTimeline::reset() {
setMaxDisplayFrames(kDefaultMaxDisplayFrames);
}
} // namespace android::frametimeline::impl