Dumpsys for FrameTimeline

Create a dumpsys interface for dumping the Display Frames and
SurfaceFrames stored within FrameTimeline. This change also adds
jankType and jankMetadata fields to SurfaceFrames and DisplayFrames to
classify the jank.

Bug: 162889501
Test: adb shell dumpsys SurfaceFlinger --frametimeline [-jank|-all]
Change-Id: Iac8c8377cf2039e982f59e6d45f7e88a42613154
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 9985253..22d9d10 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -22,12 +22,127 @@
 #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(JankType jankType) {
+    switch (jankType) {
+        case JankType::None:
+            return "None";
+        case JankType::Display:
+            return "Composer/Display - outside SF and App";
+        case JankType::SurfaceFlingerDeadlineMissed:
+            return "SurfaceFlinger Deadline Missed";
+        case JankType::AppDeadlineMissed:
+            return "App Deadline Missed";
+        case JankType::PredictionExpired:
+            return "Prediction Expired";
+        case 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);
@@ -69,29 +184,26 @@
         mPredictionState(predictionState),
         mPredictions(predictions),
         mActuals({0, 0, 0}),
-        mActualQueueTime(0) {}
+        mActualQueueTime(0),
+        mJankType(JankType::None),
+        mJankMetadata(0) {}
 
 void SurfaceFrame::setPresentState(PresentState state) {
     std::lock_guard<std::mutex> lock(mMutex);
     mPresentState = state;
 }
 
-PredictionState SurfaceFrame::getPredictionState() {
-    std::lock_guard<std::mutex> lock(mMutex);
-    return mPredictionState;
-}
-
-SurfaceFrame::PresentState SurfaceFrame::getPresentState() {
+SurfaceFrame::PresentState SurfaceFrame::getPresentState() const {
     std::lock_guard<std::mutex> lock(mMutex);
     return mPresentState;
 }
 
-TimelineItem SurfaceFrame::getActuals() {
+TimelineItem SurfaceFrame::getActuals() const {
     std::lock_guard<std::mutex> lock(mMutex);
     return mActuals;
 }
 
-nsecs_t SurfaceFrame::getActualQueueTime() {
+nsecs_t SurfaceFrame::getActualQueueTime() const {
     std::lock_guard<std::mutex> lock(mMutex);
     return mActualQueueTime;
 }
@@ -115,17 +227,62 @@
     mActuals.presentTime = presentTime;
 }
 
-void SurfaceFrame::dump(std::string& result) {
+void SurfaceFrame::setJankInfo(JankType jankType, int32_t jankMetadata) {
     std::lock_guard<std::mutex> lock(mMutex);
-    StringAppendF(&result, "Present State : %d\n", static_cast<int>(mPresentState));
-    StringAppendF(&result, "Prediction State : %d\n", static_cast<int>(mPredictionState));
-    StringAppendF(&result, "Predicted Start Time : %" PRId64 "\n", mPredictions.startTime);
-    StringAppendF(&result, "Actual Start Time : %" PRId64 "\n", mActuals.startTime);
-    StringAppendF(&result, "Actual Queue Time : %" PRId64 "\n", mActualQueueTime);
-    StringAppendF(&result, "Predicted Render Complete Time : %" PRId64 "\n", mPredictions.endTime);
-    StringAppendF(&result, "Actual Render Complete Time : %" PRId64 "\n", mActuals.endTime);
-    StringAppendF(&result, "Predicted Present Time : %" PRId64 "\n", mPredictions.presentTime);
-    StringAppendF(&result, "Actual Present Time : %" PRId64 "\n", mActuals.presentTime);
+    mJankType = jankType;
+    mJankMetadata = jankMetadata;
+}
+
+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", mLayerName.c_str());
+    if (mJankType != JankType::None) {
+        // Easily identify a janky Surface Frame in the dump
+        StringAppendF(&result, " [*] ");
+    }
+    StringAppendF(&result, "\n");
+    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() : mCurrentDisplayFrame(std::make_shared<DisplayFrame>()) {}
@@ -133,7 +290,9 @@
 FrameTimeline::DisplayFrame::DisplayFrame()
       : surfaceFlingerPredictions(TimelineItem()),
         surfaceFlingerActuals(TimelineItem()),
-        predictionState(PredictionState::None) {
+        predictionState(PredictionState::None),
+        jankType(JankType::None),
+        jankMetadata(0) {
     this->surfaceFrames.reserve(kNumSurfaceFramesInitial);
 }
 
@@ -200,10 +359,75 @@
         if (signalTime != Fence::SIGNAL_TIME_INVALID) {
             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 & EarlyPresent) {
+                    displayFrame->jankType = JankType::SurfaceFlingerEarlyLatch;
+                } else if (displayFrame->jankMetadata & LateFinish & LatePresent) {
+                    displayFrame->jankType = 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 = JankType::Display;
+                }
+            }
+            if (std::abs(sfActuals.startTime - sfPredictions.startTime) > kSFStartThreshold) {
+                displayFrame->jankMetadata |=
+                        sfActuals.startTime > sfPredictions.startTime ? LateStart : EarlyStart;
+            }
+
             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(JankType::PredictionExpired, 0);
+                        continue;
+                    } else if (predictionState == PredictionState::Valid) {
+                        const auto& actuals = surfaceFrame->getActuals();
+                        const auto& predictions = surfaceFrame->getPredictions();
+                        int32_t jankMetadata = 0;
+                        JankType jankType = 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 = JankType::SurfaceFlingerEarlyLatch;
+                        } else if (jankMetadata & LatePresent) {
+                            if (jankMetadata & EarlyFinish) {
+                                // TODO(b/169890654): Classify this properly
+                                jankType = JankType::Display;
+                            } else {
+                                jankType = JankType::AppDeadlineMissed;
+                            }
+                        }
+                        surfaceFrame->setJankInfo(jankType, jankMetadata);
+                    }
                 }
             }
         }
@@ -223,29 +447,86 @@
     mCurrentDisplayFrame = std::make_shared<DisplayFrame>();
 }
 
-void FrameTimeline::dump(std::string& result) {
+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 != 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());
-    for (const auto& displayFrame : mDisplayFrames) {
-        StringAppendF(&result, "---Display Frame---\n");
-        StringAppendF(&result, "Prediction State : %d\n",
-                      static_cast<int>(displayFrame->predictionState));
-        StringAppendF(&result, "Predicted SF wake time : %" PRId64 "\n",
-                      displayFrame->surfaceFlingerPredictions.startTime);
-        StringAppendF(&result, "Actual SF wake time : %" PRId64 "\n",
-                      displayFrame->surfaceFlingerActuals.startTime);
-        StringAppendF(&result, "Predicted SF Complete time : %" PRId64 "\n",
-                      displayFrame->surfaceFlingerPredictions.endTime);
-        StringAppendF(&result, "Actual SF Complete time : %" PRId64 "\n",
-                      displayFrame->surfaceFlingerActuals.endTime);
-        StringAppendF(&result, "Predicted Present time : %" PRId64 "\n",
-                      displayFrame->surfaceFlingerPredictions.presentTime);
-        StringAppendF(&result, "Actual Present time : %" PRId64 "\n",
-                      displayFrame->surfaceFlingerActuals.presentTime);
-        for (size_t i = 0; i < displayFrame->surfaceFrames.size(); i++) {
-            StringAppendF(&result, "Surface frame - %" PRId32 "\n", (int)i);
-            displayFrame->surfaceFrames[i]->dump(result);
+    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 == JankType::None) {
+            // Check if any Surface Frame has been janky
+            bool isJanky = false;
+            for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
+                if (surfaceFrame->getJankType() != 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);
     }
 }
 
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index cfe8170..1624c2b 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -22,10 +22,48 @@
 #include <gui/ISurfaceComposer.h>
 #include <ui/FenceTime.h>
 #include <utils/RefBase.h>
+#include <utils/String16.h>
 #include <utils/Timers.h>
+#include <utils/Vector.h>
 
 namespace android::frametimeline {
 
+/*
+ * The type of jank that is associated with a Display/Surface frame
+ */
+enum class JankType {
+    // No Jank
+    None,
+    // Jank not related to SurfaceFlinger or the App
+    Display,
+    // SF took too long on the CPU
+    SurfaceFlingerDeadlineMissed,
+    // Either App or GPU took too long on the frame
+    AppDeadlineMissed,
+    // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a jank
+    // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
+    PredictionExpired,
+    // Latching a buffer early might cause an early present of the frame
+    SurfaceFlingerEarlyLatch,
+};
+
+enum JankMetadata {
+    // Frame was presented earlier than expected
+    EarlyPresent = 0x1,
+    // Frame was presented later than expected
+    LatePresent = 0x2,
+    // App/SF started earlier than expected
+    EarlyStart = 0x4,
+    // App/SF started later than expected
+    LateStart = 0x8,
+    // App/SF finished work earlier than the deadline
+    EarlyFinish = 0x10,
+    // App/SF finished work later than the deadline
+    LateFinish = 0x20,
+    // SF was in GPU composition
+    GpuComposition = 0x40,
+};
+
 class FrameTimelineTest;
 
 /*
@@ -75,11 +113,11 @@
 
     virtual ~SurfaceFrame() = default;
 
-    virtual TimelineItem getPredictions() = 0;
-    virtual TimelineItem getActuals() = 0;
-    virtual nsecs_t getActualQueueTime() = 0;
-    virtual PresentState getPresentState() = 0;
-    virtual PredictionState getPredictionState() = 0;
+    virtual TimelineItem getPredictions() const = 0;
+    virtual TimelineItem getActuals() const = 0;
+    virtual nsecs_t getActualQueueTime() const = 0;
+    virtual PresentState getPresentState() const = 0;
+    virtual PredictionState getPredictionState() const = 0;
 
     virtual void setPresentState(PresentState state) = 0;
 
@@ -120,7 +158,11 @@
     virtual void setSfPresent(nsecs_t sfPresentTime,
                               const std::shared_ptr<FenceTime>& presentFence) = 0;
 
-    virtual void dump(std::string& result) = 0;
+    // Args:
+    // -jank : Dumps only the Display Frames that are either janky themselves
+    //         or contain janky Surface Frames.
+    // -all : Dumps the entire list of DisplayFrames and the SurfaceFrames contained within
+    virtual void parseArgs(const Vector<String16>& args, std::string& result) = 0;
 };
 
 namespace impl {
@@ -155,27 +197,33 @@
                  TimelineItem&& predictions);
     ~SurfaceFrame() = default;
 
-    TimelineItem getPredictions() override { return mPredictions; };
-    TimelineItem getActuals() override;
-    nsecs_t getActualQueueTime() override;
-    PresentState getPresentState() override;
-    PredictionState getPredictionState() override;
+    TimelineItem getPredictions() const override { return mPredictions; };
+    TimelineItem getActuals() const override;
+    nsecs_t getActualQueueTime() const override;
+    PresentState getPresentState() const override;
+    PredictionState getPredictionState() const override { return mPredictionState; };
 
     void setActualStartTime(nsecs_t actualStartTime) override;
     void setActualQueueTime(nsecs_t actualQueueTime) override;
     void setAcquireFenceTime(nsecs_t acquireFenceTime) override;
     void setPresentState(PresentState state) override;
     void setActualPresentTime(nsecs_t presentTime);
-    void dump(std::string& result);
+    void setJankInfo(JankType jankType, int32_t jankMetadata);
+    JankType getJankType() const;
+    nsecs_t getBaseTime() const;
+    // All the timestamps are dumped relative to the baseTime
+    void dump(std::string& result, const std::string& indent, nsecs_t baseTime);
 
 private:
     const std::string mLayerName;
     PresentState mPresentState GUARDED_BY(mMutex);
-    PredictionState mPredictionState GUARDED_BY(mMutex);
+    const PredictionState mPredictionState;
     const TimelineItem mPredictions;
     TimelineItem mActuals GUARDED_BY(mMutex);
-    nsecs_t mActualQueueTime;
-    std::mutex mMutex;
+    nsecs_t mActualQueueTime GUARDED_BY(mMutex);
+    mutable std::mutex mMutex;
+    JankType mJankType GUARDED_BY(mMutex);    // Enum for the type of jank
+    int32_t mJankMetadata GUARDED_BY(mMutex); // Additional details about the jank
 };
 
 class FrameTimeline : public android::frametimeline::FrameTimeline {
@@ -191,7 +239,7 @@
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime) override;
     void setSfPresent(nsecs_t sfPresentTime,
                       const std::shared_ptr<FenceTime>& presentFence) override;
-    void dump(std::string& result) override;
+    void parseArgs(const Vector<String16>& args, std::string& result) override;
 
 private:
     // Friend class for testing
@@ -215,10 +263,19 @@
         std::vector<std::unique_ptr<SurfaceFrame>> surfaceFrames;
 
         PredictionState predictionState;
+        JankType jankType = JankType::None; // Enum for the type of jank
+        int32_t jankMetadata = 0x0;         // Additional details about the jank
     };
 
     void flushPendingPresentFences() REQUIRES(mMutex);
     void finalizeCurrentDisplayFrame() REQUIRES(mMutex);
+    // BaseTime is the smallest timestamp in a DisplayFrame.
+    // Used for dumping all timestamps relative to the oldest, making it easy to read.
+    nsecs_t findBaseTime(const std::shared_ptr<DisplayFrame>&) REQUIRES(mMutex);
+    void dumpDisplayFrame(std::string& result, const std::shared_ptr<DisplayFrame>&,
+                          nsecs_t baseTime) REQUIRES(mMutex);
+    void dumpAll(std::string& result);
+    void dumpJank(std::string& result);
 
     // Sliding window of display frames. TODO(b/168072834): compare perf with fixed size array
     std::deque<std::shared_ptr<DisplayFrame>> mDisplayFrames GUARDED_BY(mMutex);
@@ -233,6 +290,14 @@
     // frame, this is a good starting size for the vector so that we can avoid the internal vector
     // resizing that happens with push_back.
     static constexpr uint32_t kNumSurfaceFramesInitial = 10;
+    // The various thresholds for App and SF. If the actual timestamp falls within the threshold
+    // compared to prediction, we don't treat it as a jank.
+    static constexpr nsecs_t kPresentThreshold =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+    static constexpr nsecs_t kDeadlineThreshold =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+    static constexpr nsecs_t kSFStartThreshold =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count();
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index df87e3b..e9eb63f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4232,7 +4232,7 @@
                 {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
                 {"--vsync"s, dumper(&SurfaceFlinger::dumpVSync)},
                 {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
-                {"--frametimeline"s, dumper([this](std::string& s) { mFrameTimeline->dump(s); })},
+                {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
         };
 
         const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
@@ -4315,6 +4315,10 @@
     mTimeStats->parseArgs(asProto, args, result);
 }
 
+void SurfaceFlinger::dumpFrameTimeline(const DumpArgs& args, std::string& result) const {
+    mFrameTimeline->parseArgs(args, result);
+}
+
 // This should only be called from the main thread.  Otherwise it would need
 // the lock and should use mCurrentState rather than mDrawingState.
 void SurfaceFlinger::logFrameStats() {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9a552d8..738e056 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -978,6 +978,7 @@
     void dumpStatsLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
     void clearStatsLocked(const DumpArgs& args, std::string& result);
     void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
+    void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
     void logFrameStats();
 
     void dumpVSync(std::string& result) const REQUIRES(mStateLock);