xref: /aosp_15_r20/frameworks/base/libs/hwui/service/GraphicsStatsService.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "GraphicsStatsService.h"
18 
19 #include <android/util/ProtoOutputStream.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
23 #include <inttypes.h>
24 #include <log/log.h>
25 #include <stats_annotations.h>
26 #include <stats_event.h>
27 #include <statslog_hwui.h>
28 #include <sys/mman.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 
33 #include "JankTracker.h"
34 #include "protos/graphicsstats.pb.h"
35 
36 namespace android {
37 namespace uirenderer {
38 
39 using namespace google::protobuf;
40 using namespace uirenderer::protos;
41 
42 constexpr int32_t sCurrentFileVersion = 1;
43 constexpr int32_t sHeaderSize = 4;
44 static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
45 
46 constexpr int sHistogramSize = ProfileData::HistogramSize();
47 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
48 
49 static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
50                                       const std::string& package, int64_t versionCode,
51                                       int64_t startTime, int64_t endTime, const ProfileData* data);
52 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
53 
54 class FileDescriptor {
55 public:
FileDescriptor(int fd)56     explicit FileDescriptor(int fd) : mFd(fd) {}
~FileDescriptor()57     ~FileDescriptor() {
58         if (mFd != -1) {
59             close(mFd);
60             mFd = -1;
61         }
62     }
valid()63     bool valid() { return mFd != -1; }
operator int()64     operator int() { return mFd; } // NOLINT(google-explicit-constructor)
65 
66 private:
67     int mFd;
68 };
69 
70 class FileOutputStreamLite : public io::ZeroCopyOutputStream {
71 public:
FileOutputStreamLite(int fd)72     explicit FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
~FileOutputStreamLite()73     virtual ~FileOutputStreamLite() {}
74 
GetErrno()75     int GetErrno() { return mCopyAdapter.mErrno; }
76 
Next(void ** data,int * size)77     virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
78 
BackUp(int count)79     virtual void BackUp(int count) override { mImpl.BackUp(count); }
80 
ByteCount() const81     virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
82 
Flush()83     bool Flush() { return mImpl.Flush(); }
84 
85 private:
86     struct FDAdapter : public io::CopyingOutputStream {
87         int mFd;
88         int mErrno = 0;
89 
FDAdapterandroid::uirenderer::FileOutputStreamLite::FDAdapter90         explicit FDAdapter(int fd) : mFd(fd) {}
~FDAdapterandroid::uirenderer::FileOutputStreamLite::FDAdapter91         virtual ~FDAdapter() {}
92 
Writeandroid::uirenderer::FileOutputStreamLite::FDAdapter93         virtual bool Write(const void* buffer, int size) override {
94             int ret;
95             while (size) {
96                 ret = TEMP_FAILURE_RETRY(write(mFd, buffer, size));
97                 if (ret <= 0) {
98                     mErrno = errno;
99                     return false;
100                 }
101                 size -= ret;
102             }
103             return true;
104         }
105     };
106 
107     FileOutputStreamLite::FDAdapter mCopyAdapter;
108     io::CopyingOutputStreamAdaptor mImpl;
109 };
110 
parseFromFile(const std::string & path,protos::GraphicsStatsProto * output)111 bool GraphicsStatsService::parseFromFile(const std::string& path,
112                                          protos::GraphicsStatsProto* output) {
113     FileDescriptor fd{open(path.c_str(), O_RDONLY)};
114     if (!fd.valid()) {
115         int err = errno;
116         // The file not existing is normal for addToDump(), so only log if
117         // we get an unexpected error
118         if (err != ENOENT) {
119             ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
120         }
121         return false;
122     }
123     struct stat sb;
124     if (fstat(fd, &sb) || sb.st_size < sHeaderSize) {
125         int err = errno;
126         // The file not existing is normal for addToDump(), so only log if
127         // we get an unexpected error
128         if (err != ENOENT) {
129             ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err,
130                   strerror(err), (int)sb.st_size);
131         }
132         return false;
133     }
134     void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
135     if (addr == MAP_FAILED) {
136         int err = errno;
137         // The file not existing is normal for addToDump(), so only log if
138         // we get an unexpected error
139         if (err != ENOENT) {
140             ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
141         }
142         return false;
143     }
144     uint32_t file_version = *reinterpret_cast<uint32_t*>(addr);
145     if (file_version != sCurrentFileVersion) {
146         ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version);
147         munmap(addr, sb.st_size);
148         return false;
149     }
150 
151     void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
152     int dataSize = sb.st_size - sHeaderSize;
153     io::ArrayInputStream input{data, dataSize};
154     bool success = output->ParseFromZeroCopyStream(&input);
155     if (!success) {
156         ALOGW("Parse failed on '%s' error='%s'", path.c_str(),
157               output->InitializationErrorString().c_str());
158     }
159     munmap(addr, sb.st_size);
160     return success;
161 }
162 
mergeProfileDataIntoProto(protos::GraphicsStatsProto * proto,uid_t uid,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)163 bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
164                                const std::string& package, int64_t versionCode, int64_t startTime,
165                                int64_t endTime, const ProfileData* data) {
166     if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
167         proto->set_stats_start(startTime);
168     }
169     if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
170         proto->set_stats_end(endTime);
171     }
172     proto->set_uid(static_cast<int32_t>(uid));
173     proto->set_package_name(package);
174     proto->set_version_code(versionCode);
175     proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
176             GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
177     auto summary = proto->mutable_summary();
178     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
179     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
180     summary->set_missed_vsync_count(summary->missed_vsync_count() +
181                                     data->jankTypeCount(kMissedVsync));
182     summary->set_high_input_latency_count(summary->high_input_latency_count() +
183                                           data->jankTypeCount(kHighInputLatency));
184     summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
185                                       data->jankTypeCount(kSlowUI));
186     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
187                                           data->jankTypeCount(kSlowSync));
188     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
189     summary->set_missed_deadline_count(summary->missed_deadline_count() +
190                                        data->jankTypeCount(kMissedDeadline));
191 
192     bool creatingHistogram = false;
193     if (proto->histogram_size() == 0) {
194         proto->mutable_histogram()->Reserve(sHistogramSize);
195         creatingHistogram = true;
196     } else if (proto->histogram_size() != sHistogramSize) {
197         ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(),
198               sHistogramSize);
199         return false;
200     }
201     int index = 0;
202     bool hitMergeError = false;
203     data->histogramForEach([&](ProfileData::HistogramEntry entry) {
204         if (hitMergeError) return;
205 
206         protos::GraphicsStatsHistogramBucketProto* bucket;
207         if (creatingHistogram) {
208             bucket = proto->add_histogram();
209             bucket->set_render_millis(entry.renderTimeMs);
210         } else {
211             bucket = proto->mutable_histogram(index);
212             if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
213                 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(),
214                       entry.renderTimeMs);
215                 hitMergeError = true;
216                 return;
217             }
218         }
219         bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
220         index++;
221     });
222     if (hitMergeError) return false;
223     // fill in GPU frame time histogram
224     creatingHistogram = false;
225     if (proto->gpu_histogram_size() == 0) {
226         proto->mutable_gpu_histogram()->Reserve(sGPUHistogramSize);
227         creatingHistogram = true;
228     } else if (proto->gpu_histogram_size() != sGPUHistogramSize) {
229         ALOGE("GPU histogram size mismatch, proto is %d expected %d", proto->gpu_histogram_size(),
230               sGPUHistogramSize);
231         return false;
232     }
233     index = 0;
234     data->histogramGPUForEach([&](ProfileData::HistogramEntry entry) {
235         if (hitMergeError) return;
236 
237         protos::GraphicsStatsHistogramBucketProto* bucket;
238         if (creatingHistogram) {
239             bucket = proto->add_gpu_histogram();
240             bucket->set_render_millis(entry.renderTimeMs);
241         } else {
242             bucket = proto->mutable_gpu_histogram(index);
243             if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
244                 ALOGW("GPU frame time mistmatch %d vs. %u", bucket->render_millis(),
245                       entry.renderTimeMs);
246                 hitMergeError = true;
247                 return;
248             }
249         }
250         bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
251         index++;
252     });
253     return !hitMergeError;
254 }
255 
findPercentile(protos::GraphicsStatsProto * proto,int percentile)256 static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) {
257     int32_t pos = percentile * proto->summary().total_frames() / 100;
258     int32_t remaining = proto->summary().total_frames() - pos;
259     for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
260         remaining -= it->frame_count();
261         if (remaining <= 0) {
262             return it->render_millis();
263         }
264     }
265     return 0;
266 }
267 
findGPUPercentile(protos::GraphicsStatsProto * proto,int percentile)268 static int32_t findGPUPercentile(protos::GraphicsStatsProto* proto, int percentile) {
269     uint32_t totalGPUFrameCount = 0;  // this is usually  proto->summary().total_frames() - 3.
270     for (auto it = proto->gpu_histogram().rbegin(); it != proto->gpu_histogram().rend(); ++it) {
271         totalGPUFrameCount += it->frame_count();
272     }
273     int32_t pos = percentile * totalGPUFrameCount / 100;
274     int32_t remaining = totalGPUFrameCount - pos;
275     for (auto it = proto->gpu_histogram().rbegin(); it != proto->gpu_histogram().rend(); ++it) {
276         remaining -= it->frame_count();
277         if (remaining <= 0) {
278             return it->render_millis();
279         }
280     }
281     return 0;
282 }
283 
dumpAsTextToFd(protos::GraphicsStatsProto * proto,int fd)284 void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) {
285     // This isn't a full validation, just enough that we can deref at will
286     if (proto->package_name().empty() || !proto->has_summary()) {
287         ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
288               proto->package_name().c_str(), proto->has_summary());
289         return;
290     }
291     dprintf(fd, "\nUID: %d", proto->uid());
292     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
293     dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
294     dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
295     dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end());
296     auto summary = proto->summary();
297     dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
298     dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
299             (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f);
300     dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
301     dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
302     dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
303     dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
304     dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
305     dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
306     dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
307     dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
308     dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
309     dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count());
310     dprintf(fd, "\nHISTOGRAM:");
311     for (const auto& it : proto->histogram()) {
312         dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
313     }
314     dprintf(fd, "\n50th gpu percentile: %dms", findGPUPercentile(proto, 50));
315     dprintf(fd, "\n90th gpu percentile: %dms", findGPUPercentile(proto, 90));
316     dprintf(fd, "\n95th gpu percentile: %dms", findGPUPercentile(proto, 95));
317     dprintf(fd, "\n99th gpu percentile: %dms", findGPUPercentile(proto, 99));
318     dprintf(fd, "\nGPU HISTOGRAM:");
319     for (const auto& it : proto->gpu_histogram()) {
320         dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
321     }
322     dprintf(fd, "\n");
323 }
324 
saveBuffer(const std::string & path,uid_t uid,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)325 void GraphicsStatsService::saveBuffer(const std::string& path, uid_t uid,
326                                       const std::string& package, int64_t versionCode,
327                                       int64_t startTime, int64_t endTime, const ProfileData* data) {
328     protos::GraphicsStatsProto statsProto;
329     if (!parseFromFile(path, &statsProto)) {
330         statsProto.Clear();
331     }
332     if (!mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime, endTime,
333                                    data)) {
334         return;
335     }
336     // Although we might not have read any data from the file, merging the existing data
337     // should always fully-initialize the proto
338     if (!statsProto.IsInitialized()) {
339         ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str());
340         return;
341     }
342     if (statsProto.package_name().empty() || !statsProto.has_summary()) {
343         ALOGE("missing package_name() '%s' summary %d", statsProto.package_name().c_str(),
344               statsProto.has_summary());
345         return;
346     }
347     int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
348     if (outFd <= 0) {
349         int err = errno;
350         ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
351         return;
352     }
353     int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
354     if (wrote != sHeaderSize) {
355         int err = errno;
356         ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", path.c_str(), wrote, err,
357               strerror(err));
358         close(outFd);
359         return;
360     }
361     {
362         FileOutputStreamLite output(outFd);
363         bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
364         if (output.GetErrno() != 0) {
365             ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(),
366                   output.GetErrno(), strerror(output.GetErrno()));
367             success = false;
368         } else if (!success) {
369             ALOGW("Serialize failed on '%s' unknown error", path.c_str());
370         }
371     }
372     close(outFd);
373 }
374 
375 class GraphicsStatsService::Dump {
376 public:
Dump(int outFd,DumpType type)377     Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
378         if (mFd == -1 && mType == DumpType::Protobuf) {
379             mType = DumpType::ProtobufStatsd;
380         }
381     }
fd()382     int fd() { return mFd; }
type()383     DumpType type() { return mType; }
proto()384     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
385     void mergeStat(const protos::GraphicsStatsProto& stat);
386     void updateProto();
387 
388 private:
389     // use package name and app version for a key
390     typedef std::tuple<uid_t, std::string, int64_t> DumpKey;
391 
392     std::map<DumpKey, protos::GraphicsStatsProto> mStats;
393     int mFd;
394     DumpType mType;
395     protos::GraphicsStatsServiceDumpProto mProto;
396 };
397 
mergeStat(const protos::GraphicsStatsProto & stat)398 void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
399     auto dumpKey = std::make_tuple(static_cast<uid_t>(stat.uid()), stat.package_name(),
400                                    stat.version_code());
401     auto findIt = mStats.find(dumpKey);
402     if (findIt == mStats.end()) {
403         mStats[dumpKey] = stat;
404     } else {
405         auto summary = findIt->second.mutable_summary();
406         summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
407         summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
408         summary->set_missed_vsync_count(summary->missed_vsync_count() +
409                                         stat.summary().missed_vsync_count());
410         summary->set_high_input_latency_count(summary->high_input_latency_count() +
411                                               stat.summary().high_input_latency_count());
412         summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
413                                           stat.summary().slow_ui_thread_count());
414         summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
415                                               stat.summary().slow_bitmap_upload_count());
416         summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
417         summary->set_missed_deadline_count(summary->missed_deadline_count() +
418                                            stat.summary().missed_deadline_count());
419         for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
420             auto bucket = findIt->second.mutable_histogram(bucketIndex);
421             bucket->set_frame_count(bucket->frame_count() +
422                                     stat.histogram(bucketIndex).frame_count());
423         }
424         for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
425              bucketIndex++) {
426             auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
427             bucket->set_frame_count(bucket->frame_count() +
428                                     stat.gpu_histogram(bucketIndex).frame_count());
429         }
430         findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
431         findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
432     }
433 }
434 
updateProto()435 void GraphicsStatsService::Dump::updateProto() {
436     for (auto& stat : mStats) {
437         mProto.add_stats()->CopyFrom(stat.second);
438     }
439 }
440 
createDump(int outFd,DumpType type)441 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
442     return new Dump(outFd, type);
443 }
444 
addToDump(Dump * dump,const std::string & path,uid_t uid,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)445 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, uid_t uid,
446                                      const std::string& package, int64_t versionCode,
447                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
448     protos::GraphicsStatsProto statsProto;
449     if (!path.empty() && !parseFromFile(path, &statsProto)) {
450         statsProto.Clear();
451     }
452     if (data && !mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime,
453                                            endTime, data)) {
454         return;
455     }
456     if (!statsProto.IsInitialized()) {
457         ALOGW("Failed to load profile data from path '%s' and data %p",
458               path.empty() ? "<empty>" : path.c_str(), data);
459         return;
460     }
461     if (dump->type() == DumpType::ProtobufStatsd) {
462         dump->mergeStat(statsProto);
463     } else if (dump->type() == DumpType::Protobuf) {
464         dump->proto().add_stats()->CopyFrom(statsProto);
465     } else {
466         dumpAsTextToFd(&statsProto, dump->fd());
467     }
468 }
469 
addToDump(Dump * dump,const std::string & path)470 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
471     protos::GraphicsStatsProto statsProto;
472     if (!parseFromFile(path, &statsProto)) {
473         return;
474     }
475     if (dump->type() == DumpType::ProtobufStatsd) {
476         dump->mergeStat(statsProto);
477     } else if (dump->type() == DumpType::Protobuf) {
478         dump->proto().add_stats()->CopyFrom(statsProto);
479     } else {
480         dumpAsTextToFd(&statsProto, dump->fd());
481     }
482 }
483 
finishDump(Dump * dump)484 void GraphicsStatsService::finishDump(Dump* dump) {
485     if (dump->type() == DumpType::Protobuf) {
486         FileOutputStreamLite stream(dump->fd());
487         dump->proto().SerializeToZeroCopyStream(&stream);
488     }
489     delete dump;
490 }
491 
492 using namespace google::protobuf;
493 
494 // Field ids taken from FrameTimingHistogram message in atoms.proto
495 #define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
496 #define FRAME_COUNTS_FIELD_NUMBER 2
497 
writeCpuHistogram(AStatsEvent * event,const uirenderer::protos::GraphicsStatsProto & stat)498 static void writeCpuHistogram(AStatsEvent* event,
499                               const uirenderer::protos::GraphicsStatsProto& stat) {
500     util::ProtoOutputStream proto;
501     for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
502         auto& bucket = stat.histogram(bucketIndex);
503         proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
504                             TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
505                     (int)bucket.render_millis());
506     }
507     for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
508         auto& bucket = stat.histogram(bucketIndex);
509         proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
510                             FRAME_COUNTS_FIELD_NUMBER /* field id */,
511                     (long long)bucket.frame_count());
512     }
513     std::vector<uint8_t> outVector;
514     proto.serializeToVector(&outVector);
515     AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
516 }
517 
writeGpuHistogram(AStatsEvent * event,const uirenderer::protos::GraphicsStatsProto & stat)518 static void writeGpuHistogram(AStatsEvent* event,
519                               const uirenderer::protos::GraphicsStatsProto& stat) {
520     util::ProtoOutputStream proto;
521     for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
522         auto& bucket = stat.gpu_histogram(bucketIndex);
523         proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
524                             TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
525                     (int)bucket.render_millis());
526     }
527     for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
528         auto& bucket = stat.gpu_histogram(bucketIndex);
529         proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
530                             FRAME_COUNTS_FIELD_NUMBER /* field id */,
531                     (long long)bucket.frame_count());
532     }
533     std::vector<uint8_t> outVector;
534     proto.serializeToVector(&outVector);
535     AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
536 }
537 
538 
finishDumpInMemory(Dump * dump,AStatsEventList * data,bool lastFullDay)539 void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data,
540                                               bool lastFullDay) {
541     dump->updateProto();
542     auto& serviceDump = dump->proto();
543     for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
544         auto& stat = serviceDump.stats(stat_index);
545         AStatsEvent* event = AStatsEventList_addStatsEvent(data);
546         AStatsEvent_setAtomId(event, stats::GRAPHICS_STATS);
547         AStatsEvent_writeString(event, stat.package_name().c_str());
548         AStatsEvent_writeInt64(event, (int64_t)stat.version_code());
549         AStatsEvent_writeInt64(event, (int64_t)stat.stats_start());
550         AStatsEvent_writeInt64(event, (int64_t)stat.stats_end());
551         AStatsEvent_writeInt32(event, (int32_t)stat.pipeline());
552         AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames());
553         AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count());
554         AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count());
555         AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count());
556         AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
557         AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count());
558         AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count());
559         writeCpuHistogram(event, stat);
560         writeGpuHistogram(event, stat);
561         // TODO: fill in UI mainline module version, when the feature is available.
562         AStatsEvent_writeInt64(event, (int64_t)0);
563         AStatsEvent_writeBool(event, !lastFullDay);
564         AStatsEvent_writeInt32(event, stat.uid());
565         AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
566         AStatsEvent_build(event);
567     }
568     delete dump;
569 }
570 
571 
572 } /* namespace uirenderer */
573 } /* namespace android */
574