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