Expose HWUI metrics via statsd

Add atom definition for HWUI stats. Implement a C++
statsd puller inside GraphicsStatsService service.
GraphicsStatsService has new private API, which
returns a serialized proto with HWUI stats grouped
by application package and version.

Test: Ran "adb shell cmd stats pull-source 10068"
Test: Ran "statsd_testdrive 10068" and it looks OK
Bug: 142665516
Change-Id: I400c0dbf9e25181d36f9018688b03d86839ac3de
diff --git a/Android.bp b/Android.bp
index 0d9cbd8..a74e3b4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -416,6 +416,13 @@
 }
 
 filegroup {
+    name: "graphicsstats_proto",
+    srcs: [
+        "libs/hwui/protos/graphicsstats.proto",
+    ],
+}
+
+filegroup {
     name: "libvibrator_aidl",
     srcs: [
         "core/java/android/os/IExternalVibrationController.aidl",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..f6680f3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -336,7 +336,7 @@
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10068
+    // Next: 10069
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -405,6 +405,7 @@
         VmsClientStats vms_client_stats = 10065;
         NotificationRemoteViews notification_remote_views = 10066;
         DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+        GraphicsStats graphics_stats = 10068;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -7553,3 +7554,69 @@
     optional int32 permission_flags = 4;
 }
 
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+    GL = 0;
+    VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+    // The package name of the app
+    optional string package_name = 1;
+
+    // The version code of the app
+    optional int64 version_code = 2;
+
+    // The start & end timestamps in UTC as
+    // milliseconds since January 1, 1970
+    // Compatible with java.util.Date#setTime()
+    optional int64 stats_start = 3;
+
+    optional int64 stats_end = 4;
+
+    // HWUI renders pipeline type: GL or Vulkan.
+    optional PipelineType pipeline = 5;
+
+    // Distinct frame count.
+    optional int32 total_frames = 6;
+
+    // Number of "missed vsync" events.
+    optional int32 missed_vsync_count = 7;
+
+    // Number of frames in triple-buffering scenario (high input latency)
+    optional int32 high_input_latency_count = 8;
+
+    // Number of "slow UI thread" events.
+    optional int32 slow_ui_thread_count = 9;
+
+    // Number of "slow bitmap upload" events.
+    optional int32 slow_bitmap_upload_count = 10;
+
+    // Number of "slow draw" events.
+    optional int32 slow_draw_count = 11;
+
+    // Number of frames that missed their deadline (aka, visibly janked)
+    optional int32 missed_deadline_count = 12;
+
+    // The frame time histogram for the package
+    optional FrameTimingHistogram cpu_histogram = 13
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // The gpu frame time histogram for the package
+    optional FrameTimingHistogram gpu_histogram = 14
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // UI mainline module version.
+    optional int64 version_ui_module = 15;
+
+    // If true, these are HWUI stats for up to a 24h period for a given app from today.
+    // If false, these are HWUI stats for a 24h period for a given app from the last complete
+    // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+    // more apps are running / rendering.
+    optional bool is_today = 16;
+}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // The package name of the app
@@ -54,6 +58,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ProfileData.h"
+#include "Properties.h"
 
 #include <cinttypes>
 
@@ -102,6 +103,7 @@
         mGPUFrameCounts[i] >>= divider;
         mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
     }
+    mPipelineType = other.mPipelineType;
 }
 
 void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
     mTotalFrameCount = 0;
     mJankFrameCount = 0;
     mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mPipelineType = Properties::getRenderPipelineType();
 }
 
 void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 #include <utils/Timers.h>
@@ -65,6 +66,7 @@
     uint32_t jankFrameCount() const { return mJankFrameCount; }
     nsecs_t statsStartTime() const { return mStatStartTime; }
     uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+    RenderPipelineType pipelineType() const { return mPipelineType; }
 
     struct HistogramEntry {
         uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
     uint32_t mTotalFrameCount;
     uint32_t mJankFrameCount;
     nsecs_t mStatStartTime;
+
+    // true if HWUI renders with Vulkan pipeline
+    RenderPipelineType mPipelineType;
 };
 
 // For testing
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
+
     // The package name of the app
     optional string package_name = 1;
 
@@ -49,6 +54,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
 
 #include "GraphicsStatsService.h"
 
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
 namespace android {
 namespace uirenderer {
 
 using namespace google::protobuf;
+using namespace uirenderer::protos;
 
 constexpr int32_t sCurrentFileVersion = 1;
 constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
-                                      const std::string& package, int64_t versionCode,
-                                      int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+                                      int64_t versionCode, int64_t startTime, int64_t endTime,
+                                      const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -57,7 +61,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+    operator int() { return mFd; }  // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -167,6 +171,8 @@
     }
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
+    proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+            GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
     auto summary = proto->mutable_summary();
     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
                                           data->jankTypeCount(kSlowSync));
     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
-    summary->set_missed_deadline_count(summary->missed_deadline_count()
-            + data->jankTypeCount(kMissedDeadline));
+    summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                       data->jankTypeCount(kMissedDeadline));
 
     bool creatingHistogram = false;
     if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
 
 class GraphicsStatsService::Dump {
 public:
-    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+        if (mFd == -1 && mType == DumpType::Protobuf) {
+            mType = DumpType::ProtobufStatsd;
+        }
+    }
     int fd() { return mFd; }
     DumpType type() { return mType; }
     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+    void mergeStat(const protos::GraphicsStatsProto& stat);
+    void updateProto();
 
 private:
+    // use package name and app version for a key
+    typedef std::pair<std::string, int64_t> DumpKey;
+
+    std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
     DumpType mType;
     protos::GraphicsStatsServiceDumpProto mProto;
 };
 
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto findIt = mStats.find(dumpKey);
+    if (findIt == mStats.end()) {
+        mStats[dumpKey] = stat;
+    } else {
+        auto summary = findIt->second.mutable_summary();
+        summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+        summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+        summary->set_missed_vsync_count(summary->missed_vsync_count() +
+                                        stat.summary().missed_vsync_count());
+        summary->set_high_input_latency_count(summary->high_input_latency_count() +
+                                              stat.summary().high_input_latency_count());
+        summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+                                          stat.summary().slow_ui_thread_count());
+        summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+                                              stat.summary().slow_bitmap_upload_count());
+        summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+        summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                           stat.summary().missed_deadline_count());
+        for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+            auto bucket = findIt->second.mutable_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.histogram(bucketIndex).frame_count());
+        }
+        for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+             bucketIndex++) {
+            auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.gpu_histogram(bucketIndex).frame_count());
+        }
+        findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+        findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+    }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+    for (auto& stat : mStats) {
+        mProto.add_stats()->CopyFrom(stat.second);
+    }
+}
+
 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
     return new Dump(outFd, type);
 }
@@ -396,8 +454,9 @@
               path.empty() ? "<empty>" : path.c_str(), data);
         return;
     }
-
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
     if (!parseFromFile(path, &statsProto)) {
         return;
     }
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
     delete dump;
 }
 
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+    explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+    virtual ~MemOutputStreamLite() {}
+
+    virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+    virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+    virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+    bool Flush() { return mImpl.Flush(); }
+
+    void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+        int bufferOffset = 0;
+        int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+        int totalDataLeft = totalSize;
+        for (auto& it : mCopyAdapter.mBuffers) {
+            int bufferSize = std::min(totalDataLeft, (int)it.size());  // last buffer is not full
+            reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+            bufferOffset += bufferSize;
+            totalDataLeft -= bufferSize;
+        }
+    }
+
+private:
+    struct MemAdapter : public io::CopyingOutputStream {
+        // Data is stored in an array of buffers.
+        // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+        std::vector<std::vector<unsigned char>> mBuffers;
+        int mBuffersSize = 0;                     // total bytes allocated in mBuffers
+        int mCurrentBufferUnusedSize = 0;         // unused bytes in the last buffer mBuffers.back()
+        unsigned char* mCurrentBuffer = nullptr;  // pointer to next free byte in mBuffers.back()
+
+        explicit MemAdapter() {}
+        virtual ~MemAdapter() {}
+
+        virtual bool Write(const void* buffer, int size) override {
+            while (size > 0) {
+                if (0 == mCurrentBufferUnusedSize) {
+                    mCurrentBufferUnusedSize =
+                            std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+                    mBuffers.emplace_back();
+                    mBuffers.back().resize(mCurrentBufferUnusedSize);
+                    mCurrentBuffer = mBuffers.back().data();
+                    mBuffersSize += mCurrentBufferUnusedSize;
+                }
+                int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+                memcpy(mCurrentBuffer, buffer, dataMoved);
+                mCurrentBufferUnusedSize -= dataMoved;
+                mCurrentBuffer += dataMoved;
+                buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+                size -= dataMoved;
+            }
+            return true;
+        }
+    };
+
+    MemOutputStreamLite::MemAdapter mCopyAdapter;
+    io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                              void* param2) {
+    MemOutputStreamLite stream;
+    dump->updateProto();
+    bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+    delete dump;
+    if (!success) {
+        return;
+    }
+    stream.copyData(reader, param1, param2);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
 class GraphicsStatsProto;
 }
 
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+                             void* param1, void* param2);
+
 /*
  * The exported entry points used by GraphicsStatsService.java in f/b/services/core
  *
@@ -40,6 +43,7 @@
     enum class DumpType {
         Text,
         Protobuf,
+        ProtobufStatsd,
     };
 
     ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
                                       int64_t startTime, int64_t endTime, const ProfileData* data);
     ANDROID_API static void addToDump(Dump* dump, const std::string& path);
     ANDROID_API static void finishDump(Dump* dump);
+    ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                               void* param2);
 
     // Visible for testing
     static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
 import android.view.IGraphicsStatsCallback;
 
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -78,6 +80,8 @@
     private static final int SAVE_BUFFER = 1;
     private static final int DELETE_OLD = 2;
 
+    private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
     // This isn't static because we need this to happen after registerNativeMethods, however
     // the class is loaded (and thus static ctor happens) before that occurs.
     private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
                 return true;
             }
         });
+        nativeInit();
     }
 
     /**
@@ -186,6 +191,86 @@
         return pfd;
     }
 
+    // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+    // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+    // current day.
+    // This method is invoked from native code only.
+    @SuppressWarnings({"UnusedDeclaration"})
+    private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+        int uid = Binder.getCallingUid();
+
+        // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+        // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+        // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+        if (uid != AID_STATSD) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new FastPrintWriter(sw);
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+                pw.flush();
+                throw new RemoteException(sw.toString());
+            }
+        }
+
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return pullGraphicsStatsImpl(lastFullDay);
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    private long pullGraphicsStatsImpl(boolean lastFullDay) {
+        long targetDay;
+        if (lastFullDay) {
+            // Get stats from yesterday. Stats stay constant, because the day is over.
+            targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+        } else {
+            // Get stats from today. Stats may change as more apps are run today.
+            targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+        }
+
+        // Find active buffers for targetDay.
+        ArrayList<HistoricalBuffer> buffers;
+        synchronized (mLock) {
+            buffers = new ArrayList<>(mActive.size());
+            for (int i = 0; i < mActive.size(); i++) {
+                ActiveBuffer buffer = mActive.get(i);
+                if (buffer.mInfo.startTime == targetDay) {
+                    try {
+                        buffers.add(new HistoricalBuffer(buffer));
+                    } catch (IOException ex) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Dump active and historic buffers for targetDay in a serialized
+        // GraphicsStatsServiceDumpProto proto.
+        long dump = nCreateDump(-1, true);
+        try {
+            synchronized (mFileAccessLock) {
+                HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+                buffers.clear();
+                String subPath = String.format("%d", targetDay);
+                File dateDir = new File(mGraphicsStatsDir, subPath);
+                if (dateDir.exists()) {
+                    for (File pkg : dateDir.listFiles()) {
+                        for (File version : pkg.listFiles()) {
+                            File data = new File(version, "total");
+                            if (skipList.contains(data)) {
+                                continue;
+                            }
+                            nAddToDump(dump, data.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        } finally {
+            return nFinishDumpInMemory(dump);
+        }
+    }
+
     private ParcelFileDescriptor getPfd(MemoryFile file) {
         try {
             if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
         }
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestructor();
+    }
+
+    private native void nativeInit();
+    private static native void nativeDestructor();
+
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
     private static native void nAddToDump(long dump, String path, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
+    private static native long nFinishDumpInMemory(long dump);
     private static native void nSaveBuffer(String path, String packageName, long versionCode,
             long startTime, long endTime, byte[] data);
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1ad6e86..03969b0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
     ],
 
     srcs: [
+        ":graphicsstats_proto",
         "BroadcastRadio/JavaRef.cpp",
         "BroadcastRadio/NativeCallbackThread.cpp",
         "BroadcastRadio/BroadcastRadioService.cpp",
@@ -103,6 +104,11 @@
         "libinputflinger",
         "libinputflinger_base",
         "libinputservice",
+        "libprotobuf-cpp-lite",
+        "libprotoutil",
+        "libstatspull",
+        "libstatssocket",
+        "libstatslog",
         "libschedulerservicehidl",
         "libsensorservice",
         "libsensorservicehidl",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..9353fbd 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <JankTracker.h>
 #include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
 
 namespace android {
 
@@ -77,6 +87,20 @@
     GraphicsStatsService::finishDump(dump);
 }
 
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    std::vector<uint8_t>* result = new std::vector<uint8_t>();
+    GraphicsStatsService::finishDumpInMemory(dump,
+        [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+            std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+            if (outBuffer->size() < totalSize) {
+                outBuffer->resize(totalSize);
+            }
+            std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+        }, env, result);
+    return reinterpret_cast<jlong>(result);
+}
+
 static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
         jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,173 @@
     GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
 }
 
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+        }
+    }
+    return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data,
+                                      const void* cookie) {
+    JNIEnv* env = getJNIEnv();
+    if (!env) {
+        return false;
+    }
+    if (gGraphicsStatsServiceObject == nullptr) {
+        ALOGE("Failed to get graphicsstats service");
+        return false;
+    }
+
+    for (bool lastFullDay : {true, false}) {
+        jlong jdata = (jlong) env->CallLongMethod(
+                    gGraphicsStatsServiceObject,
+                    gGraphicsStatsService_pullGraphicsStatsMethodID,
+                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            ALOGE("Failed to invoke graphicsstats service");
+            return false;
+        }
+        if (!jdata) {
+            // null means data is not available for that day.
+            continue;
+        }
+        android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+        std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+        std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+        int dataSize = buffer->size();
+        if (!dataSize) {
+            // Data is not available for that day.
+            continue;
+        }
+        io::ArrayInputStream input{buffer->data(), dataSize};
+        bool success = serviceDump.ParseFromZeroCopyStream(&input);
+        if (!success) {
+            ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+                  serviceDump.InitializationErrorString().c_str(), dataSize);
+            return false;
+        }
+
+        for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+            auto& stat = serviceDump.stats(stat_index);
+            stats_event* event = add_stats_event_to_pull_data(data);
+            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+            stats_event_write_string8(event, stat.package_name().c_str());
+            stats_event_write_int64(event, (int64_t)stat.version_code());
+            stats_event_write_int64(event, (int64_t)stat.stats_start());
+            stats_event_write_int64(event, (int64_t)stat.stats_end());
+            stats_event_write_int32(event, (int32_t)stat.pipeline());
+            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            writeCpuHistogram(event, stat);
+            writeGpuHistogram(event, stat);
+            // TODO: fill in UI mainline module version, when the feature is available.
+            stats_event_write_int64(event, (int64_t)0);
+            stats_event_write_bool(event, !lastFullDay);
+            stats_event_build(event);
+        }
+    }
+    return true;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+    gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+                                   .additive_fields = nullptr,
+                                   .additive_fields_size = 0};
+    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+            &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+    //TODO: Unregister the puller callback when a new API is available.
+    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+    gGraphicsStatsServiceObject = nullptr;
+}
+
 static const JNINativeMethod sMethods[] = {
     { "nGetAshmemSize", "()I", (void*) getAshmemSize },
     { "nCreateDump", "(IZ)J", (void*) createDump },
     { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
     { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
     { "nFinishDump", "(J)V", (void*) finishDump },
+    { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
     { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+    { "nativeInit", "()V", (void*) nativeInit },
+    { "nativeDestructor",   "()V",     (void*)nativeDestructor }
 };
 
 int register_android_server_GraphicsStatsService(JNIEnv* env)
 {
+    jclass graphicsStatsService_class = FindClassOrDie(env,
+            "com/android/server/GraphicsStatsService");
+    gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+            graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
     return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
                                     sMethods, NELEM(sMethods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android