SurfaceFlinger TimeStats Metrics

Add timestats metrics for SurfaceFlinger. Keep track of global metrics
like total frames, missed frames, frames fellback to client
compositions, etc, as well as layer timing metrics like the delta
combination of postTime, desiredPresentTime, acqureTime, latchTime,
presentTime, etc. This metric is aimed at GMScore.

Test: dumpsys SurfaceFlinger --timestats [go/sf-timestats for more args]
Bug: b/70388650
Change-Id: I6e4545aef62f7893020533a4e7521541ea453ecd
Merged-In: I6e4545aef62f7893020533a4e7521541ea453ecd
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 5b1e631..6a69844 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -45,6 +45,7 @@
         "libpdx_default_transport",
         "libprotobuf-cpp-lite",
         "libsync",
+        "libtimestats_proto",
         "libui",
         "libutils",
         "libvulkan",
@@ -124,6 +125,7 @@
         "SurfaceFlinger.cpp",
         "SurfaceInterceptor.cpp",
         "SurfaceTracing.cpp",
+        "TimeStats/TimeStats.cpp",
         "Transform.cpp",
     ],
 }
@@ -172,6 +174,7 @@
         "liblayers_proto",
         "liblog",
         "libsurfaceflinger",
+        "libtimestats_proto",
         "libutils",
     ],
     static_libs: [
@@ -213,5 +216,6 @@
 
 subdirs = [
     "layerproto",
+    "TimeStats/timestatsproto",
     "tests",
 ]
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 7fd9d01..4c3844e 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -316,6 +316,9 @@
     nsecs_t desiredPresentTime = mConsumer->getTimestamp();
     mFrameTracker.setDesiredPresentTime(desiredPresentTime);
 
+    const std::string layerName(getName().c_str());
+    mTimeStats.setDesiredTime(layerName, mCurrentFrameNumber, desiredPresentTime);
+
     std::shared_ptr<FenceTime> frameReadyFence = mConsumer->getCurrentFenceTime();
     if (frameReadyFence->isValid()) {
         mFrameTracker.setFrameReadyFence(std::move(frameReadyFence));
@@ -326,12 +329,15 @@
     }
 
     if (presentFence->isValid()) {
+        mTimeStats.setPresentFence(layerName, mCurrentFrameNumber, presentFence);
         mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
     } else {
         // The HWC doesn't support present fences, so use the refresh
         // timestamp instead.
-        mFrameTracker.setActualPresentTime(
-                mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY));
+        const nsecs_t actualPresentTime =
+                mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY);
+        mTimeStats.setPresentTime(layerName, mCurrentFrameNumber, actualPresentTime);
+        mFrameTracker.setActualPresentTime(actualPresentTime);
     }
 
     mFrameTracker.advanceFrame();
@@ -441,6 +447,7 @@
         // and return early
         if (queuedBuffer) {
             Mutex::Autolock lock(mQueueItemLock);
+            mTimeStats.removeTimeRecord(getName().c_str(), mQueueItems[0].mFrameNumber);
             mQueueItems.removeAt(0);
             android_atomic_dec(&mQueuedFrames);
         }
@@ -454,6 +461,7 @@
             Mutex::Autolock lock(mQueueItemLock);
             mQueueItems.clear();
             android_atomic_and(0, &mQueuedFrames);
+            mTimeStats.clearLayerRecord(getName().c_str());
         }
 
         // Once we have hit this state, the shadow queue may no longer
@@ -474,10 +482,15 @@
         // Remove any stale buffers that have been dropped during
         // updateTexImage
         while (mQueueItems[0].mFrameNumber != currentFrameNumber) {
+            mTimeStats.removeTimeRecord(getName().c_str(), mQueueItems[0].mFrameNumber);
             mQueueItems.removeAt(0);
             android_atomic_dec(&mQueuedFrames);
         }
 
+        const std::string layerName(getName().c_str());
+        mTimeStats.setAcquireFence(layerName, currentFrameNumber, mQueueItems[0].mFenceTime);
+        mTimeStats.setLatchTime(layerName, currentFrameNumber, latchTime);
+
         mQueueItems.removeAt(0);
     }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index bbc974d..2802fc7 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1532,10 +1532,16 @@
 void Layer::onDisconnect() {
     Mutex::Autolock lock(mFrameEventHistoryMutex);
     mFrameEventHistory.onDisconnect();
+    mTimeStats.onDisconnect(getName().c_str());
 }
 
 void Layer::addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
                                      FrameEventHistoryDelta* outDelta) {
+    if (newTimestamps) {
+        mTimeStats.setPostTime(getName().c_str(), newTimestamps->frameNumber,
+                               newTimestamps->postedTime);
+    }
+
     Mutex::Autolock lock(mFrameEventHistoryMutex);
     if (newTimestamps) {
         // If there are any unsignaled fences in the aquire timeline at this
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index be3967b..0b15b67 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -41,6 +41,7 @@
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
 #include "SurfaceFlinger.h"
+#include "TimeStats/TimeStats.h"
 #include "Transform.h"
 
 #include <layerproto/LayerProtoHeader.h>
@@ -737,6 +738,8 @@
     FenceTimeline mAcquireTimeline;
     FenceTimeline mReleaseTimeline;
 
+    TimeStats& mTimeStats = TimeStats::getInstance();
+
     // main thread
     int mActiveBufferSlot;
     sp<GraphicBuffer> mActiveBuffer;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f736c8c..e3a4706 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1470,9 +1470,13 @@
                             Fence::SIGNAL_TIME_PENDING);
             ATRACE_INT("FrameMissed", static_cast<int>(frameMissed));
             if (mPropagateBackpressure && frameMissed) {
+                mTimeStats.incrementMissedFrames(true);
                 signalLayerUpdate();
                 break;
             }
+            if (frameMissed) {
+                mTimeStats.incrementMissedFrames(false);
+            }
 
             // Now that we're going to make it to the handleMessageTransaction()
             // call below it's safe to call updateVrFlinger(), which will
@@ -1771,6 +1775,11 @@
         mAnimFrameTracker.advanceFrame();
     }
 
+    mTimeStats.incrementTotalFrames();
+    if (mHadClientComposition) {
+        mTimeStats.incrementClientCompositionFrames();
+    }
+
     if (getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY) &&
             hw->getPowerMode() == HWC_POWER_MODE_OFF) {
         return;
@@ -3824,12 +3833,6 @@
         size_t index = 0;
         size_t numArgs = args.size();
 
-        if (asProto) {
-            LayersProto layersProto = dumpProtoInfo(LayerVector::StateSet::Current);
-            result.append(layersProto.SerializeAsString().c_str(), layersProto.ByteSize());
-            dumpAll = false;
-        }
-
         if (numArgs) {
             if ((index < numArgs) &&
                     (args[index] == String16("--list"))) {
@@ -3906,10 +3909,21 @@
                 mLayerStats.dump(result);
                 dumpAll = false;
             }
+
+            if ((index < numArgs) && (args[index] == String16("--timestats"))) {
+                index++;
+                mTimeStats.parseArgs(asProto, args, index, result);
+                dumpAll = false;
+            }
         }
 
         if (dumpAll) {
-            dumpAllLocked(args, index, result);
+            if (asProto) {
+                LayersProto layersProto = dumpProtoInfo(LayerVector::StateSet::Current);
+                result.append(layersProto.SerializeAsString().c_str(), layersProto.ByteSize());
+            } else {
+                dumpAllLocked(args, index, result);
+            }
         }
 
         if (locked) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 33706da..a1c8a33 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -63,6 +63,7 @@
 #include "SurfaceInterceptor.h"
 #include "SurfaceTracing.h"
 #include "StartPropertySetThread.h"
+#include "TimeStats/TimeStats.h"
 #include "VSyncModulator.h"
 
 #include "DisplayHardware/HWC2.h"
@@ -815,6 +816,7 @@
             std::make_unique<impl::SurfaceInterceptor>(this);
     SurfaceTracing mTracing;
     LayerStats mLayerStats;
+    TimeStats& mTimeStats = TimeStats::getInstance();
     bool mUseHwcVirtualDisplays = false;
 
     // Restrict layers to use two buffers in their bufferqueues.
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
new file mode 100644
index 0000000..5f2dd32
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -0,0 +1,498 @@
+/*
+ * Copyright 2018 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 "TimeStats"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "TimeStats.h"
+
+#include <android-base/stringprintf.h>
+
+#include <log/log.h>
+
+#include <utils/String8.h>
+#include <utils/Trace.h>
+
+#include <algorithm>
+#include <regex>
+
+namespace android {
+
+TimeStats& TimeStats::getInstance() {
+    static std::unique_ptr<TimeStats> sInstance;
+    static std::once_flag sOnceFlag;
+
+    std::call_once(sOnceFlag, [] { sInstance.reset(new TimeStats); });
+    return *sInstance.get();
+}
+
+void TimeStats::parseArgs(bool asProto, const Vector<String16>& args, size_t& index,
+                          String8& result) {
+    ATRACE_CALL();
+
+    if (args.size() > index + 10) {
+        ALOGD("Invalid args count");
+        return;
+    }
+
+    std::unordered_map<std::string, int32_t> argsMap;
+    while (index < args.size()) {
+        argsMap[std::string(String8(args[index]).c_str())] = index;
+        ++index;
+    }
+
+    if (argsMap.count("-disable")) {
+        disable();
+    }
+
+    if (argsMap.count("-dump")) {
+        int64_t maxLayers = 0;
+        auto iter = argsMap.find("-maxlayers");
+        if (iter != argsMap.end() && iter->second + 1 < static_cast<int32_t>(args.size())) {
+            maxLayers = strtol(String8(args[iter->second + 1]).c_str(), nullptr, 10);
+            maxLayers = std::clamp(maxLayers, int64_t(0), int64_t(UINT32_MAX));
+        }
+
+        dump(asProto, static_cast<uint32_t>(maxLayers), result);
+    }
+
+    if (argsMap.count("-clear")) {
+        clear();
+    }
+
+    if (argsMap.count("-enable")) {
+        enable();
+    }
+}
+
+void TimeStats::incrementTotalFrames() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    timeStats.totalFrames++;
+}
+
+void TimeStats::incrementMissedFrames(bool propagateBackpressure) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (propagateBackpressure) {
+        timeStats.totalFrames--;
+    }
+    timeStats.missedFrames++;
+}
+
+void TimeStats::incrementClientCompositionFrames() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    timeStats.clientCompositionFrames++;
+}
+
+bool TimeStats::recordReadyLocked(const std::string& layerName, TimeRecord* timeRecord) {
+    if (!timeRecord->ready) {
+        ALOGV("[%s]-[%" PRIu64 "]-presentFence is still not received", layerName.c_str(),
+              timeRecord->frameNumber);
+        return false;
+    }
+
+    if (timeRecord->acquireFence != nullptr) {
+        if (timeRecord->acquireFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+            return false;
+        }
+        if (timeRecord->acquireFence->getSignalTime() != Fence::SIGNAL_TIME_INVALID) {
+            timeRecord->acquireTime = timeRecord->acquireFence->getSignalTime();
+            timeRecord->acquireFence = nullptr;
+        } else {
+            ALOGV("[%s]-[%" PRIu64 "]-acquireFence signal time is invalid", layerName.c_str(),
+                  timeRecord->frameNumber);
+        }
+    }
+
+    if (timeRecord->presentFence != nullptr) {
+        if (timeRecord->presentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+            return false;
+        }
+        if (timeRecord->presentFence->getSignalTime() != Fence::SIGNAL_TIME_INVALID) {
+            timeRecord->presentTime = timeRecord->presentFence->getSignalTime();
+            timeRecord->presentFence = nullptr;
+        } else {
+            ALOGV("[%s]-[%" PRIu64 "]-presentFence signal time invalid", layerName.c_str(),
+                  timeRecord->frameNumber);
+        }
+    }
+
+    return true;
+}
+
+static int32_t msBetween(nsecs_t start, nsecs_t end) {
+    int64_t delta = (end - start) / 1000000;
+    delta = std::clamp(delta, int64_t(INT32_MIN), int64_t(INT32_MAX));
+    return static_cast<int32_t>(delta);
+}
+
+void TimeStats::flushAvailableRecordsToStatsLocked(const std::string& layerName) {
+    ATRACE_CALL();
+
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
+    std::vector<TimeRecord>& timeRecords = layerRecord.timeRecords;
+    while (!timeRecords.empty()) {
+        if (!recordReadyLocked(layerName, &timeRecords[0])) break;
+        ALOGV("[%s]-[%" PRIu64 "]-presentFenceTime[%" PRId64 "]", layerName.c_str(),
+              timeRecords[0].frameNumber, timeRecords[0].presentTime);
+
+        if (prevTimeRecord.ready) {
+            if (!timeStats.stats.count(layerName)) {
+                timeStats.stats[layerName].layerName = layerName;
+                timeStats.stats[layerName].statsStart = static_cast<int64_t>(std::time(0));
+            }
+            TimeStatsHelper::TimeStatsLayer& timeStatsLayer = timeStats.stats[layerName];
+            timeStatsLayer.totalFrames++;
+
+            const int32_t postToPresentMs =
+                    msBetween(timeRecords[0].postTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-post2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, postToPresentMs);
+            timeStatsLayer.deltas["post2present"].insert(postToPresentMs);
+
+            const int32_t acquireToPresentMs =
+                    msBetween(timeRecords[0].acquireTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-acquire2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, acquireToPresentMs);
+            timeStatsLayer.deltas["acquire2present"].insert(acquireToPresentMs);
+
+            const int32_t latchToPresentMs =
+                    msBetween(timeRecords[0].latchTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-latch2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, latchToPresentMs);
+            timeStatsLayer.deltas["latch2present"].insert(latchToPresentMs);
+
+            const int32_t desiredToPresentMs =
+                    msBetween(timeRecords[0].desiredTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-desired2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, desiredToPresentMs);
+            timeStatsLayer.deltas["desired2present"].insert(desiredToPresentMs);
+
+            const int32_t presentToPresentMs =
+                    msBetween(prevTimeRecord.presentTime, timeRecords[0].presentTime);
+            ALOGV("[%s]-[%" PRIu64 "]-present2present[%d]", layerName.c_str(),
+                  timeRecords[0].frameNumber, presentToPresentMs);
+            timeStatsLayer.deltas["present2present"].insert(presentToPresentMs);
+
+            timeStats.stats[layerName].statsEnd = static_cast<int64_t>(std::time(0));
+        }
+        prevTimeRecord = timeRecords[0];
+        // TODO(zzyiwei): change timeRecords to use std::deque
+        timeRecords.erase(timeRecords.begin());
+        layerRecord.waitData--;
+    }
+}
+
+static bool layerNameIsValid(const std::string& layerName) {
+    // This regular expression captures the following layer names for instance:
+    // 1) StatusBat#0
+    // 2) NavigationBar#1
+    // 3) com.*#0
+    // 4) SurfaceView - com.*#0
+    // Using [-\\s\t]+ for the conjunction part between SurfaceView and com.* is
+    // a bit more robust in case there's a slight change.
+    // The layer name would only consist of . / $ _ 0-9 a-z A-Z in most cases.
+    std::regex re("(((SurfaceView[-\\s\\t]+)?com\\.[./$\\w]+)|((Status|Navigation)Bar))#\\d+");
+    return std::regex_match(layerName.begin(), layerName.end(), re);
+}
+
+void TimeStats::setPostTime(const std::string& layerName, uint64_t frameNumber, nsecs_t postTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-PostTime[%" PRId64 "]", layerName.c_str(), frameNumber, postTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName) && !layerNameIsValid(layerName)) {
+        return;
+    }
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    if (layerRecord.timeRecords.size() == MAX_NUM_TIME_RECORDS) {
+        ALOGV("[%s]-timeRecords is already at its maximum size[%zu]", layerName.c_str(),
+              MAX_NUM_TIME_RECORDS);
+        // TODO(zzyiwei): if this happens, there must be a present fence missing
+        // or waitData is not in the correct position. Need to think out a
+        // reasonable way to recover from this state.
+        return;
+    }
+    // For most media content, the acquireFence is invalid because the buffer is
+    // ready at the queueBuffer stage. In this case, acquireTime should be given
+    // a default value as postTime.
+    TimeRecord timeRecord = {
+            .frameNumber = frameNumber,
+            .postTime = postTime,
+            .acquireTime = postTime,
+    };
+    layerRecord.timeRecords.push_back(timeRecord);
+    if (layerRecord.waitData < 0 ||
+        layerRecord.waitData >= static_cast<int32_t>(layerRecord.timeRecords.size()))
+        layerRecord.waitData = layerRecord.timeRecords.size() - 1;
+}
+
+void TimeStats::setLatchTime(const std::string& layerName, uint64_t frameNumber,
+                             nsecs_t latchTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-LatchTime[%" PRId64 "]", layerName.c_str(), frameNumber, latchTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.latchTime = latchTime;
+    }
+}
+
+void TimeStats::setDesiredTime(const std::string& layerName, uint64_t frameNumber,
+                               nsecs_t desiredTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-DesiredTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          desiredTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.desiredTime = desiredTime;
+    }
+}
+
+void TimeStats::setAcquireTime(const std::string& layerName, uint64_t frameNumber,
+                               nsecs_t acquireTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-AcquireTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          acquireTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.acquireTime = acquireTime;
+    }
+}
+
+void TimeStats::setAcquireFence(const std::string& layerName, uint64_t frameNumber,
+                                const std::shared_ptr<FenceTime>& acquireFence) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-AcquireFenceTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          acquireFence->getSignalTime());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.acquireFence = acquireFence;
+    }
+}
+
+void TimeStats::setPresentTime(const std::string& layerName, uint64_t frameNumber,
+                               nsecs_t presentTime) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-PresentTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          presentTime);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.presentTime = presentTime;
+        timeRecord.ready = true;
+        layerRecord.waitData++;
+    }
+
+    flushAvailableRecordsToStatsLocked(layerName);
+}
+
+void TimeStats::setPresentFence(const std::string& layerName, uint64_t frameNumber,
+                                const std::shared_ptr<FenceTime>& presentFence) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-PresentFenceTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+          presentFence->getSignalTime());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+    if (timeRecord.frameNumber == frameNumber) {
+        timeRecord.presentFence = presentFence;
+        timeRecord.ready = true;
+        layerRecord.waitData++;
+    }
+
+    flushAvailableRecordsToStatsLocked(layerName);
+}
+
+void TimeStats::onDisconnect(const std::string& layerName) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-onDisconnect", layerName.c_str());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    flushAvailableRecordsToStatsLocked(layerName);
+    timeStatsTracker.erase(layerName);
+}
+
+void TimeStats::clearLayerRecord(const std::string& layerName) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-clearLayerRecord", layerName.c_str());
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    layerRecord.timeRecords.clear();
+    layerRecord.prevTimeRecord.ready = false;
+    layerRecord.waitData = -1;
+}
+
+void TimeStats::removeTimeRecord(const std::string& layerName, uint64_t frameNumber) {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+    ALOGV("[%s]-[%" PRIu64 "]-removeTimeRecord", layerName.c_str(), frameNumber);
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!timeStatsTracker.count(layerName)) return;
+    LayerRecord& layerRecord = timeStatsTracker[layerName];
+    size_t removeAt = 0;
+    for (const TimeRecord& record : layerRecord.timeRecords) {
+        if (record.frameNumber == frameNumber) break;
+        removeAt++;
+    }
+    if (removeAt == layerRecord.timeRecords.size()) return;
+    layerRecord.timeRecords.erase(layerRecord.timeRecords.begin() + removeAt);
+    if (layerRecord.waitData > static_cast<int32_t>(removeAt)) {
+        --layerRecord.waitData;
+    }
+}
+
+void TimeStats::enable() {
+    if (mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    ALOGD("Enabled");
+    mEnabled.store(true);
+    timeStats.statsStart = static_cast<int64_t>(std::time(0));
+}
+
+void TimeStats::disable() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    ALOGD("Disabled");
+    mEnabled.store(false);
+    timeStats.statsEnd = static_cast<int64_t>(std::time(0));
+}
+
+void TimeStats::clear() {
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    ALOGD("Cleared");
+    timeStats.dumpStats.clear();
+    timeStats.stats.clear();
+    timeStats.statsStart = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
+    timeStats.statsEnd = 0;
+    timeStats.totalFrames = 0;
+    timeStats.missedFrames = 0;
+    timeStats.clientCompositionFrames = 0;
+}
+
+bool TimeStats::isEnabled() {
+    return mEnabled.load();
+}
+
+void TimeStats::dump(bool asProto, uint32_t maxLayers, String8& result) {
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (timeStats.statsStart == 0) {
+        return;
+    }
+
+    timeStats.statsEnd = static_cast<int64_t>(std::time(0));
+
+    // TODO(zzyiwei): refactor dumpStats into TimeStatsHelper
+    timeStats.dumpStats.clear();
+    for (auto& ele : timeStats.stats) {
+        timeStats.dumpStats.push_back(&ele.second);
+    }
+
+    std::sort(timeStats.dumpStats.begin(), timeStats.dumpStats.end(),
+              [](TimeStatsHelper::TimeStatsLayer* const& l,
+                 TimeStatsHelper::TimeStatsLayer* const& r) {
+                  return l->totalFrames > r->totalFrames;
+              });
+
+    if (maxLayers != 0 && maxLayers < timeStats.dumpStats.size()) {
+        timeStats.dumpStats.resize(maxLayers);
+    }
+
+    if (asProto) {
+        dumpAsProtoLocked(result);
+    } else {
+        dumpAsTextLocked(result);
+    }
+}
+
+void TimeStats::dumpAsTextLocked(String8& result) {
+    ALOGD("Dumping TimeStats as text");
+    result.append(timeStats.toString().c_str());
+    result.append("\n");
+}
+
+void TimeStats::dumpAsProtoLocked(String8& result) {
+    ALOGD("Dumping TimeStats as proto");
+    SFTimeStatsGlobalProto timeStatsProto = timeStats.toProto();
+    result.append(timeStatsProto.SerializeAsString().c_str(), timeStatsProto.ByteSize());
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
new file mode 100644
index 0000000..2410265
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <timestatsproto/TimeStatsHelper.h>
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <ui/FenceTime.h>
+
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+class String8;
+
+class TimeStats {
+    // TODO(zzyiwei): Bound the timeStatsTracker with weighted LRU
+    // static const size_t MAX_NUM_LAYER_RECORDS = 200;
+    static const size_t MAX_NUM_TIME_RECORDS = 64;
+
+    struct TimeRecord {
+        bool ready = false;
+        uint64_t frameNumber = 0;
+        nsecs_t postTime = 0;
+        nsecs_t latchTime = 0;
+        nsecs_t acquireTime = 0;
+        nsecs_t desiredTime = 0;
+        nsecs_t presentTime = 0;
+        std::shared_ptr<FenceTime> acquireFence;
+        std::shared_ptr<FenceTime> presentFence;
+    };
+
+    struct LayerRecord {
+        // This is the index in timeRecords, at which the timestamps for that
+        // specific frame are still not fully received. This is not waiting for
+        // fences to signal, but rather waiting to receive those fences/timestamps.
+        int32_t waitData = -1;
+        TimeRecord prevTimeRecord;
+        std::vector<TimeRecord> timeRecords;
+    };
+
+public:
+    static TimeStats& getInstance();
+    void parseArgs(bool asProto, const Vector<String16>& args, size_t& index, String8& result);
+    void incrementTotalFrames();
+    void incrementMissedFrames(bool propagateBackpressure);
+    void incrementClientCompositionFrames();
+
+    void setPostTime(const std::string& layerName, uint64_t frameNumber, nsecs_t postTime);
+    void setLatchTime(const std::string& layerName, uint64_t frameNumber, nsecs_t latchTime);
+    void setDesiredTime(const std::string& layerName, uint64_t frameNumber, nsecs_t desiredTime);
+    void setAcquireTime(const std::string& layerName, uint64_t frameNumber, nsecs_t acquireTime);
+    void setAcquireFence(const std::string& layerName, uint64_t frameNumber,
+                         const std::shared_ptr<FenceTime>& acquireFence);
+    void setPresentTime(const std::string& layerName, uint64_t frameNumber, nsecs_t presentTime);
+    void setPresentFence(const std::string& layerName, uint64_t frameNumber,
+                         const std::shared_ptr<FenceTime>& presentFence);
+    void onDisconnect(const std::string& layerName);
+    void clearLayerRecord(const std::string& layerName);
+    void removeTimeRecord(const std::string& layerName, uint64_t frameNumber);
+
+private:
+    TimeStats() = default;
+
+    bool recordReadyLocked(const std::string& layerName, TimeRecord* timeRecord);
+    void flushAvailableRecordsToStatsLocked(const std::string& layerName);
+
+    void enable();
+    void disable();
+    void clear();
+    bool isEnabled();
+    void dump(bool asProto, uint32_t maxLayer, String8& result);
+    void dumpAsTextLocked(String8& result);
+    void dumpAsProtoLocked(String8& result);
+
+    std::atomic<bool> mEnabled = false;
+    std::mutex mMutex;
+    TimeStatsHelper::TimeStatsGlobal timeStats;
+    std::unordered_map<std::string, LayerRecord> timeStatsTracker;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
new file mode 100644
index 0000000..66aa719
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
@@ -0,0 +1,55 @@
+cc_library_shared {
+    name: "libtimestats_proto",
+    vendor_available: true,
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "TimeStatsHelper.cpp",
+        "timestats.proto",
+    ],
+
+    shared_libs: [
+        "android.hardware.graphics.common@1.1",
+        "libui",
+        "libprotobuf-cpp-lite",
+        "libbase",
+        "liblog",
+    ],
+
+    proto: {
+        export_proto_headers: true,
+    },
+
+    cppflags: [
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wno-format",
+        "-Wno-c++98-compat-pedantic",
+        "-Wno-float-conversion",
+        "-Wno-disabled-macro-expansion",
+        "-Wno-float-equal",
+        "-Wno-sign-conversion",
+        "-Wno-padded",
+        "-Wno-old-style-cast",
+        "-Wno-undef",
+    ],
+
+}
+
+java_library_static {
+    name: "timestatsprotosnano",
+    host_supported: true,
+    proto: {
+        type: "nano",
+    },
+    srcs: ["*.proto"],
+    no_framework_libs: true,
+    target: {
+        android: {
+            jarjar_rules: "jarjar-rules.txt",
+        },
+        host: {
+            static_libs: ["libprotobuf-java-nano"],
+        },
+    },
+}
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
new file mode 100644
index 0000000..ec0570d
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android-base/stringprintf.h>
+#include <timestatsproto/TimeStatsHelper.h>
+
+#include <array>
+#include <regex>
+
+#define HISTOGRAM_SIZE 85
+
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+
+namespace android {
+namespace surfaceflinger {
+
+// Time buckets for histogram, the calculated time deltas will be lower bounded
+// to the buckets in this array.
+static const std::array<int32_t, HISTOGRAM_SIZE> histogramConfig =
+        {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,
+         17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  32,  33,
+         34,  36,  38,  40,  42,  44,  46,  48,  50,  54,  58,  62,  66,  70,  74,  78,  82,
+         86,  90,  94,  98,  102, 106, 110, 114, 118, 122, 126, 130, 134, 138, 142, 146, 150,
+         200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000};
+
+void TimeStatsHelper::Histogram::insert(int32_t delta) {
+    if (delta < 0) return;
+    // std::lower_bound won't work on out of range values
+    if (delta > histogramConfig[HISTOGRAM_SIZE - 1]) {
+        hist[histogramConfig[HISTOGRAM_SIZE - 1]]++;
+        return;
+    }
+    auto iter = std::lower_bound(histogramConfig.begin(), histogramConfig.end(), delta);
+    hist[*iter]++;
+}
+
+float TimeStatsHelper::Histogram::averageTime() {
+    int64_t ret = 0;
+    int64_t count = 0;
+    for (auto ele : hist) {
+        count += ele.second;
+        ret += ele.first * ele.second;
+    }
+    return static_cast<float>(ret) / count;
+}
+
+std::string TimeStatsHelper::Histogram::toString() {
+    std::string result;
+    for (int32_t i = 0; i < HISTOGRAM_SIZE; ++i) {
+        int32_t bucket = histogramConfig[i];
+        int32_t count = (hist.count(bucket) == 0) ? 0 : hist[bucket];
+        StringAppendF(&result, "%dms=%d ", bucket, count);
+    }
+    result.back() = '\n';
+    return result;
+}
+
+static std::string getPackageName(const std::string& layerName) {
+    // This regular expression captures the following for instance:
+    // StatusBar in StatusBar#0
+    // com.appname in com.appname/com.appname.activity#0
+    // com.appname in SurfaceView - com.appname/com.appname.activity#0
+    const std::regex re("(?:SurfaceView[-\\s\\t]+)?([^/]+).*#\\d+");
+    std::smatch match;
+    if (std::regex_match(layerName.begin(), layerName.end(), match, re)) {
+        // There must be a match for group 1 otherwise the whole string is not
+        // matched and the above will return false
+        return match[1];
+    }
+    return "";
+}
+
+std::string TimeStatsHelper::TimeStatsLayer::toString() {
+    std::string result = "";
+    StringAppendF(&result, "layerName = %s\n", layerName.c_str());
+    packageName = getPackageName(layerName);
+    StringAppendF(&result, "packageName = %s\n", packageName.c_str());
+    StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+    StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+    StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+    if (deltas.find("present2present") != deltas.end()) {
+        StringAppendF(&result, "averageFPS = %.3f\n",
+                      1000.0 / deltas["present2present"].averageTime());
+    }
+    for (auto ele : deltas) {
+        StringAppendF(&result, "%s histogram is as below:\n", ele.first.c_str());
+        StringAppendF(&result, "%s", ele.second.toString().c_str());
+    }
+
+    return result;
+}
+
+std::string TimeStatsHelper::TimeStatsGlobal::toString() {
+    std::string result = "SurfaceFlinger TimeStats:\n";
+    StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+    StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+    StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+    StringAppendF(&result, "missedFrames= %d\n", missedFrames);
+    StringAppendF(&result, "clientCompositionFrames= %d\n", clientCompositionFrames);
+    StringAppendF(&result, "TimeStats for each layer is as below:\n");
+    for (auto ele : dumpStats) {
+        StringAppendF(&result, "%s", ele->toString().c_str());
+    }
+
+    return result;
+}
+
+SFTimeStatsLayerProto TimeStatsHelper::TimeStatsLayer::toProto() {
+    SFTimeStatsLayerProto layerProto;
+    layerProto.set_layer_name(layerName);
+    packageName = getPackageName(layerName);
+    layerProto.set_package_name(packageName);
+    layerProto.set_stats_start(statsStart);
+    layerProto.set_stats_end(statsEnd);
+    layerProto.set_total_frames(totalFrames);
+    for (auto ele : deltas) {
+        SFTimeStatsDeltaProto* deltaProto = layerProto.add_deltas();
+        deltaProto->set_delta_name(ele.first);
+        SFTimeStatsHistogramBucketProto* histProto = deltaProto->add_histograms();
+        for (auto histEle : ele.second.hist) {
+            histProto->set_render_millis(histEle.first);
+            histProto->set_frame_count(histEle.second);
+        }
+    }
+    return layerProto;
+}
+
+SFTimeStatsGlobalProto TimeStatsHelper::TimeStatsGlobal::toProto() {
+    SFTimeStatsGlobalProto globalProto;
+    globalProto.set_stats_start(statsStart);
+    globalProto.set_stats_end(statsEnd);
+    globalProto.set_total_frames(totalFrames);
+    globalProto.set_missed_frames(missedFrames);
+    globalProto.set_client_composition_frames(clientCompositionFrames);
+    for (auto ele : dumpStats) {
+        SFTimeStatsLayerProto* layerProto = globalProto.add_stats();
+        layerProto->CopyFrom(ele->toProto());
+    }
+    return globalProto;
+}
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
new file mode 100644
index 0000000..c876f21
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <math/vec4.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace surfaceflinger {
+
+class TimeStatsHelper {
+public:
+    class Histogram {
+    public:
+        // Key is the delta time between timestamps
+        // Value is the number of appearances of that delta
+        std::unordered_map<int32_t, int32_t> hist;
+
+        void insert(int32_t delta);
+        float averageTime();
+        std::string toString();
+    };
+
+    class TimeStatsLayer {
+    public:
+        std::string layerName;
+        std::string packageName;
+        int64_t statsStart = 0;
+        int64_t statsEnd = 0;
+        int32_t totalFrames = 0;
+        std::unordered_map<std::string, Histogram> deltas;
+
+        std::string toString();
+        SFTimeStatsLayerProto toProto();
+    };
+
+    class TimeStatsGlobal {
+    public:
+        int64_t statsStart = 0;
+        int64_t statsEnd = 0;
+        int32_t totalFrames = 0;
+        int32_t missedFrames = 0;
+        int32_t clientCompositionFrames = 0;
+        std::unordered_map<std::string, TimeStatsLayer> stats;
+        std::vector<TimeStatsLayer*> dumpStats;
+
+        std::string toString();
+        SFTimeStatsGlobalProto toProto();
+    };
+};
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
new file mode 100644
index 0000000..fe0d150
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Projectlayerproto/LayerProtoHeader.h
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// pragma is used here to disable the warnings emitted from the protobuf
+// headers. By adding #pragma before including layer.pb.h, it supresses
+// protobuf warnings, but allows the rest of the files to continuing using
+// the current flags.
+// This file should be included instead of directly including layer.b.h
+#pragma GCC system_header
+#include <timestats.pb.h>
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
new file mode 100644
index 0000000..40043a8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
new file mode 100644
index 0000000..a8f6fa8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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.
+ */
+
+syntax = "proto2";
+
+package android.surfaceflinger;
+
+option optimize_for = LITE_RUNTIME;
+
+// frameworks/base/core/proto/android/service/sftimestats.proto is based on
+// this proto. Please only make valid protobuf changes to these messages, and
+// keep the other file in sync with this one.
+
+message SFTimeStatsGlobalProto {
+  // The start & end timestamps in UTC as
+  // milliseconds since January 1, 1970
+  optional int64 stats_start = 1;
+  optional int64 stats_end = 2;
+  // Total frames
+  optional int32 total_frames = 3;
+  // Total missed frames of SurfaceFlinger.
+  optional int32 missed_frames = 4;
+  // Total frames fallback to client composition.
+  optional int32 client_composition_frames = 5;
+
+  repeated SFTimeStatsLayerProto stats = 6;
+}
+
+message SFTimeStatsLayerProto {
+  // The layer name
+  optional string layer_name = 1;
+  // The package name
+  optional string package_name = 2;
+  // The start & end timestamps in UTC as
+  // milliseconds since January 1, 1970
+  optional int64 stats_start = 3;
+  optional int64 stats_end = 4;
+  // Distinct frame count.
+  optional int32 total_frames = 5;
+
+  repeated SFTimeStatsDeltaProto deltas = 6;
+}
+
+message SFTimeStatsDeltaProto {
+  // Name of the time interval
+  optional string delta_name = 1;
+  // Histogram of the delta time
+  repeated SFTimeStatsHistogramBucketProto histograms = 2;
+}
+
+message SFTimeStatsHistogramBucketProto {
+  // Lower bound of render time in milliseconds.
+  optional int32 render_millis = 1;
+  // Number of frames in the bucket.
+  optional int32 frame_count = 2;
+}
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 7523399..322e8a0 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -36,6 +36,7 @@
         "liblayers_proto",
         "liblog",
         "libprotobuf-cpp-full",
+        "libtimestats_proto",
         "libui",
         "libutils",
     ]
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 00bc621..520df2d 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -25,6 +25,7 @@
         "liblog",
         "libnativewindow",
         "libsync",
+        "libtimestats_proto",
         "libui",
         "libutils",
     ],