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