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 <errno.h>
20 #include <fcntl.h>
21 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
22 #include <inttypes.h>
23 #include <log/log.h>
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28
29 #include <android/util/ProtoOutputStream.h>
30 #include <stats_event.h>
31 #include <statslog.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, const std::string& package,
50 int64_t versionCode, int64_t startTime, int64_t endTime,
51 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,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)163 bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
164 int64_t versionCode, int64_t startTime, int64_t endTime,
165 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_package_name(package);
173 proto->set_version_code(versionCode);
174 proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
175 GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
176 auto summary = proto->mutable_summary();
177 summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
178 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
179 summary->set_missed_vsync_count(summary->missed_vsync_count() +
180 data->jankTypeCount(kMissedVsync));
181 summary->set_high_input_latency_count(summary->high_input_latency_count() +
182 data->jankTypeCount(kHighInputLatency));
183 summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
184 data->jankTypeCount(kSlowUI));
185 summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
186 data->jankTypeCount(kSlowSync));
187 summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
188 summary->set_missed_deadline_count(summary->missed_deadline_count() +
189 data->jankTypeCount(kMissedDeadline));
190
191 bool creatingHistogram = false;
192 if (proto->histogram_size() == 0) {
193 proto->mutable_histogram()->Reserve(sHistogramSize);
194 creatingHistogram = true;
195 } else if (proto->histogram_size() != sHistogramSize) {
196 ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(),
197 sHistogramSize);
198 return false;
199 }
200 int index = 0;
201 bool hitMergeError = false;
202 data->histogramForEach([&](ProfileData::HistogramEntry entry) {
203 if (hitMergeError) return;
204
205 protos::GraphicsStatsHistogramBucketProto* bucket;
206 if (creatingHistogram) {
207 bucket = proto->add_histogram();
208 bucket->set_render_millis(entry.renderTimeMs);
209 } else {
210 bucket = proto->mutable_histogram(index);
211 if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
212 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(),
213 entry.renderTimeMs);
214 hitMergeError = true;
215 return;
216 }
217 }
218 bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
219 index++;
220 });
221 if (hitMergeError) return false;
222 // fill in GPU frame time histogram
223 creatingHistogram = false;
224 if (proto->gpu_histogram_size() == 0) {
225 proto->mutable_gpu_histogram()->Reserve(sGPUHistogramSize);
226 creatingHistogram = true;
227 } else if (proto->gpu_histogram_size() != sGPUHistogramSize) {
228 ALOGE("GPU histogram size mismatch, proto is %d expected %d", proto->gpu_histogram_size(),
229 sGPUHistogramSize);
230 return false;
231 }
232 index = 0;
233 data->histogramGPUForEach([&](ProfileData::HistogramEntry entry) {
234 if (hitMergeError) return;
235
236 protos::GraphicsStatsHistogramBucketProto* bucket;
237 if (creatingHistogram) {
238 bucket = proto->add_gpu_histogram();
239 bucket->set_render_millis(entry.renderTimeMs);
240 } else {
241 bucket = proto->mutable_gpu_histogram(index);
242 if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
243 ALOGW("GPU frame time mistmatch %d vs. %u", bucket->render_millis(),
244 entry.renderTimeMs);
245 hitMergeError = true;
246 return;
247 }
248 }
249 bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
250 index++;
251 });
252 return !hitMergeError;
253 }
254
findPercentile(protos::GraphicsStatsProto * proto,int percentile)255 static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) {
256 int32_t pos = percentile * proto->summary().total_frames() / 100;
257 int32_t remaining = proto->summary().total_frames() - pos;
258 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
259 remaining -= it->frame_count();
260 if (remaining <= 0) {
261 return it->render_millis();
262 }
263 }
264 return 0;
265 }
266
findGPUPercentile(protos::GraphicsStatsProto * proto,int percentile)267 static int32_t findGPUPercentile(protos::GraphicsStatsProto* proto, int percentile) {
268 uint32_t totalGPUFrameCount = 0; // this is usually proto->summary().total_frames() - 3.
269 for (auto it = proto->gpu_histogram().rbegin(); it != proto->gpu_histogram().rend(); ++it) {
270 totalGPUFrameCount += it->frame_count();
271 }
272 int32_t pos = percentile * totalGPUFrameCount / 100;
273 int32_t remaining = totalGPUFrameCount - pos;
274 for (auto it = proto->gpu_histogram().rbegin(); it != proto->gpu_histogram().rend(); ++it) {
275 remaining -= it->frame_count();
276 if (remaining <= 0) {
277 return it->render_millis();
278 }
279 }
280 return 0;
281 }
282
dumpAsTextToFd(protos::GraphicsStatsProto * proto,int fd)283 void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) {
284 // This isn't a full validation, just enough that we can deref at will
285 if (proto->package_name().empty() || !proto->has_summary()) {
286 ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
287 proto->package_name().c_str(), proto->has_summary());
288 return;
289 }
290 dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
291 dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
292 dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
293 dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end());
294 auto summary = proto->summary();
295 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
296 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
297 (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f);
298 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
299 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
300 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
301 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
302 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
303 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
304 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
305 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
306 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
307 dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count());
308 dprintf(fd, "\nHISTOGRAM:");
309 for (const auto& it : proto->histogram()) {
310 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
311 }
312 dprintf(fd, "\n50th gpu percentile: %dms", findGPUPercentile(proto, 50));
313 dprintf(fd, "\n90th gpu percentile: %dms", findGPUPercentile(proto, 90));
314 dprintf(fd, "\n95th gpu percentile: %dms", findGPUPercentile(proto, 95));
315 dprintf(fd, "\n99th gpu percentile: %dms", findGPUPercentile(proto, 99));
316 dprintf(fd, "\nGPU HISTOGRAM:");
317 for (const auto& it : proto->gpu_histogram()) {
318 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
319 }
320 dprintf(fd, "\n");
321 }
322
saveBuffer(const std::string & path,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)323 void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
324 int64_t versionCode, int64_t startTime, int64_t endTime,
325 const ProfileData* data) {
326 protos::GraphicsStatsProto statsProto;
327 if (!parseFromFile(path, &statsProto)) {
328 statsProto.Clear();
329 }
330 if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
331 return;
332 }
333 // Although we might not have read any data from the file, merging the existing data
334 // should always fully-initialize the proto
335 if (!statsProto.IsInitialized()) {
336 ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str());
337 return;
338 }
339 if (statsProto.package_name().empty() || !statsProto.has_summary()) {
340 ALOGE("missing package_name() '%s' summary %d", statsProto.package_name().c_str(),
341 statsProto.has_summary());
342 return;
343 }
344 int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
345 if (outFd <= 0) {
346 int err = errno;
347 ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
348 return;
349 }
350 int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
351 if (wrote != sHeaderSize) {
352 int err = errno;
353 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", path.c_str(), wrote, err,
354 strerror(err));
355 close(outFd);
356 return;
357 }
358 {
359 FileOutputStreamLite output(outFd);
360 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
361 if (output.GetErrno() != 0) {
362 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(),
363 output.GetErrno(), strerror(output.GetErrno()));
364 success = false;
365 } else if (!success) {
366 ALOGW("Serialize failed on '%s' unknown error", path.c_str());
367 }
368 }
369 close(outFd);
370 }
371
372 class GraphicsStatsService::Dump {
373 public:
Dump(int outFd,DumpType type)374 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
375 if (mFd == -1 && mType == DumpType::Protobuf) {
376 mType = DumpType::ProtobufStatsd;
377 }
378 }
fd()379 int fd() { return mFd; }
type()380 DumpType type() { return mType; }
proto()381 protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
382 void mergeStat(const protos::GraphicsStatsProto& stat);
383 void updateProto();
384
385 private:
386 // use package name and app version for a key
387 typedef std::pair<std::string, int64_t> DumpKey;
388
389 std::map<DumpKey, protos::GraphicsStatsProto> mStats;
390 int mFd;
391 DumpType mType;
392 protos::GraphicsStatsServiceDumpProto mProto;
393 };
394
mergeStat(const protos::GraphicsStatsProto & stat)395 void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
396 auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
397 auto findIt = mStats.find(dumpKey);
398 if (findIt == mStats.end()) {
399 mStats[dumpKey] = stat;
400 } else {
401 auto summary = findIt->second.mutable_summary();
402 summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
403 summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
404 summary->set_missed_vsync_count(summary->missed_vsync_count() +
405 stat.summary().missed_vsync_count());
406 summary->set_high_input_latency_count(summary->high_input_latency_count() +
407 stat.summary().high_input_latency_count());
408 summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
409 stat.summary().slow_ui_thread_count());
410 summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
411 stat.summary().slow_bitmap_upload_count());
412 summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
413 summary->set_missed_deadline_count(summary->missed_deadline_count() +
414 stat.summary().missed_deadline_count());
415 for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
416 auto bucket = findIt->second.mutable_histogram(bucketIndex);
417 bucket->set_frame_count(bucket->frame_count() +
418 stat.histogram(bucketIndex).frame_count());
419 }
420 for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
421 bucketIndex++) {
422 auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
423 bucket->set_frame_count(bucket->frame_count() +
424 stat.gpu_histogram(bucketIndex).frame_count());
425 }
426 findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
427 findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
428 }
429 }
430
updateProto()431 void GraphicsStatsService::Dump::updateProto() {
432 for (auto& stat : mStats) {
433 mProto.add_stats()->CopyFrom(stat.second);
434 }
435 }
436
createDump(int outFd,DumpType type)437 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
438 return new Dump(outFd, type);
439 }
440
addToDump(Dump * dump,const std::string & path,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)441 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
442 const std::string& package, int64_t versionCode,
443 int64_t startTime, int64_t endTime, const ProfileData* data) {
444 protos::GraphicsStatsProto statsProto;
445 if (!path.empty() && !parseFromFile(path, &statsProto)) {
446 statsProto.Clear();
447 }
448 if (data &&
449 !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
450 return;
451 }
452 if (!statsProto.IsInitialized()) {
453 ALOGW("Failed to load profile data from path '%s' and data %p",
454 path.empty() ? "<empty>" : path.c_str(), data);
455 return;
456 }
457 if (dump->type() == DumpType::ProtobufStatsd) {
458 dump->mergeStat(statsProto);
459 } else if (dump->type() == DumpType::Protobuf) {
460 dump->proto().add_stats()->CopyFrom(statsProto);
461 } else {
462 dumpAsTextToFd(&statsProto, dump->fd());
463 }
464 }
465
addToDump(Dump * dump,const std::string & path)466 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
467 protos::GraphicsStatsProto statsProto;
468 if (!parseFromFile(path, &statsProto)) {
469 return;
470 }
471 if (dump->type() == DumpType::ProtobufStatsd) {
472 dump->mergeStat(statsProto);
473 } else if (dump->type() == DumpType::Protobuf) {
474 dump->proto().add_stats()->CopyFrom(statsProto);
475 } else {
476 dumpAsTextToFd(&statsProto, dump->fd());
477 }
478 }
479
finishDump(Dump * dump)480 void GraphicsStatsService::finishDump(Dump* dump) {
481 if (dump->type() == DumpType::Protobuf) {
482 FileOutputStreamLite stream(dump->fd());
483 dump->proto().SerializeToZeroCopyStream(&stream);
484 }
485 delete dump;
486 }
487
488 using namespace google::protobuf;
489
490 // Field ids taken from FrameTimingHistogram message in atoms.proto
491 #define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
492 #define FRAME_COUNTS_FIELD_NUMBER 2
493
writeCpuHistogram(AStatsEvent * event,const uirenderer::protos::GraphicsStatsProto & stat)494 static void writeCpuHistogram(AStatsEvent* event,
495 const uirenderer::protos::GraphicsStatsProto& stat) {
496 util::ProtoOutputStream proto;
497 for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
498 auto& bucket = stat.histogram(bucketIndex);
499 proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
500 TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
501 (int)bucket.render_millis());
502 }
503 for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
504 auto& bucket = stat.histogram(bucketIndex);
505 proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
506 FRAME_COUNTS_FIELD_NUMBER /* field id */,
507 (long long)bucket.frame_count());
508 }
509 std::vector<uint8_t> outVector;
510 proto.serializeToVector(&outVector);
511 AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
512 }
513
writeGpuHistogram(AStatsEvent * event,const uirenderer::protos::GraphicsStatsProto & stat)514 static void writeGpuHistogram(AStatsEvent* event,
515 const uirenderer::protos::GraphicsStatsProto& stat) {
516 util::ProtoOutputStream proto;
517 for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
518 auto& bucket = stat.gpu_histogram(bucketIndex);
519 proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
520 TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
521 (int)bucket.render_millis());
522 }
523 for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
524 auto& bucket = stat.gpu_histogram(bucketIndex);
525 proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
526 FRAME_COUNTS_FIELD_NUMBER /* field id */,
527 (long long)bucket.frame_count());
528 }
529 std::vector<uint8_t> outVector;
530 proto.serializeToVector(&outVector);
531 AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
532 }
533
534
finishDumpInMemory(Dump * dump,AStatsEventList * data,bool lastFullDay)535 void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data,
536 bool lastFullDay) {
537 dump->updateProto();
538 auto& serviceDump = dump->proto();
539 for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
540 auto& stat = serviceDump.stats(stat_index);
541 AStatsEvent* event = AStatsEventList_addStatsEvent(data);
542 AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS);
543 AStatsEvent_writeString(event, stat.package_name().c_str());
544 AStatsEvent_writeInt64(event, (int64_t)stat.version_code());
545 AStatsEvent_writeInt64(event, (int64_t)stat.stats_start());
546 AStatsEvent_writeInt64(event, (int64_t)stat.stats_end());
547 AStatsEvent_writeInt32(event, (int32_t)stat.pipeline());
548 AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames());
549 AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count());
550 AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count());
551 AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count());
552 AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
553 AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count());
554 AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count());
555 writeCpuHistogram(event, stat);
556 writeGpuHistogram(event, stat);
557 // TODO: fill in UI mainline module version, when the feature is available.
558 AStatsEvent_writeInt64(event, (int64_t)0);
559 AStatsEvent_writeBool(event, !lastFullDay);
560 AStatsEvent_build(event);
561 }
562 delete dump;
563 }
564
565
566 } /* namespace uirenderer */
567 } /* namespace android */
568