| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "GraphicsStatsService.h" |
| |
| #include <android/util/ProtoOutputStream.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <google/protobuf/io/zero_copy_stream_impl_lite.h> |
| #include <inttypes.h> |
| #include <log/log.h> |
| #include <stats_event.h> |
| #include <statslog_hwui.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "JankTracker.h" |
| #include "protos/graphicsstats.pb.h" |
| |
| namespace android { |
| namespace uirenderer { |
| |
| using namespace google::protobuf; |
| using namespace uirenderer::protos; |
| |
| constexpr int32_t sCurrentFileVersion = 1; |
| constexpr int32_t sHeaderSize = 4; |
| static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong"); |
| |
| constexpr int sHistogramSize = ProfileData::HistogramSize(); |
| constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize(); |
| |
| static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, |
| int64_t versionCode, int64_t startTime, int64_t endTime, |
| const ProfileData* data); |
| static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd); |
| |
| class FileDescriptor { |
| public: |
| explicit FileDescriptor(int fd) : mFd(fd) {} |
| ~FileDescriptor() { |
| if (mFd != -1) { |
| close(mFd); |
| mFd = -1; |
| } |
| } |
| bool valid() { return mFd != -1; } |
| operator int() { return mFd; } // NOLINT(google-explicit-constructor) |
| |
| private: |
| int mFd; |
| }; |
| |
| class FileOutputStreamLite : public io::ZeroCopyOutputStream { |
| public: |
| explicit FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {} |
| virtual ~FileOutputStreamLite() {} |
| |
| int GetErrno() { return mCopyAdapter.mErrno; } |
| |
| virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } |
| |
| virtual void BackUp(int count) override { mImpl.BackUp(count); } |
| |
| virtual int64 ByteCount() const override { return mImpl.ByteCount(); } |
| |
| bool Flush() { return mImpl.Flush(); } |
| |
| private: |
| struct FDAdapter : public io::CopyingOutputStream { |
| int mFd; |
| int mErrno = 0; |
| |
| explicit FDAdapter(int fd) : mFd(fd) {} |
| virtual ~FDAdapter() {} |
| |
| virtual bool Write(const void* buffer, int size) override { |
| int ret; |
| while (size) { |
| ret = TEMP_FAILURE_RETRY(write(mFd, buffer, size)); |
| if (ret <= 0) { |
| mErrno = errno; |
| return false; |
| } |
| size -= ret; |
| } |
| return true; |
| } |
| }; |
| |
| FileOutputStreamLite::FDAdapter mCopyAdapter; |
| io::CopyingOutputStreamAdaptor mImpl; |
| }; |
| |
| bool GraphicsStatsService::parseFromFile(const std::string& path, |
| protos::GraphicsStatsProto* output) { |
| FileDescriptor fd{open(path.c_str(), O_RDONLY)}; |
| if (!fd.valid()) { |
| int err = errno; |
| // The file not existing is normal for addToDump(), so only log if |
| // we get an unexpected error |
| if (err != ENOENT) { |
| ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err)); |
| } |
| return false; |
| } |
| struct stat sb; |
| if (fstat(fd, &sb) || sb.st_size < sHeaderSize) { |
| int err = errno; |
| // The file not existing is normal for addToDump(), so only log if |
| // we get an unexpected error |
| if (err != ENOENT) { |
| ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err, |
| strerror(err), (int)sb.st_size); |
| } |
| return false; |
| } |
| void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); |
| if (addr == MAP_FAILED) { |
| int err = errno; |
| // The file not existing is normal for addToDump(), so only log if |
| // we get an unexpected error |
| if (err != ENOENT) { |
| ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err)); |
| } |
| return false; |
| } |
| uint32_t file_version = *reinterpret_cast<uint32_t*>(addr); |
| if (file_version != sCurrentFileVersion) { |
| ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version); |
| munmap(addr, sb.st_size); |
| return false; |
| } |
| |
| void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize; |
| int dataSize = sb.st_size - sHeaderSize; |
| io::ArrayInputStream input{data, dataSize}; |
| bool success = output->ParseFromZeroCopyStream(&input); |
| if (!success) { |
| ALOGW("Parse failed on '%s' error='%s'", path.c_str(), |
| output->InitializationErrorString().c_str()); |
| } |
| munmap(addr, sb.st_size); |
| return success; |
| } |
| |
| bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, |
| int64_t versionCode, int64_t startTime, int64_t endTime, |
| const ProfileData* data) { |
| if (proto->stats_start() == 0 || proto->stats_start() > startTime) { |
| proto->set_stats_start(startTime); |
| } |
| if (proto->stats_end() == 0 || proto->stats_end() < endTime) { |
| proto->set_stats_end(endTime); |
| } |
| proto->set_package_name(package); |
| proto->set_version_code(versionCode); |
| proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ? |
| GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN); |
| auto summary = proto->mutable_summary(); |
| summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); |
| summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); |
| summary->set_missed_vsync_count(summary->missed_vsync_count() + |
| data->jankTypeCount(kMissedVsync)); |
| summary->set_high_input_latency_count(summary->high_input_latency_count() + |
| data->jankTypeCount(kHighInputLatency)); |
| summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + |
| data->jankTypeCount(kSlowUI)); |
| summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + |
| data->jankTypeCount(kSlowSync)); |
| summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); |
| summary->set_missed_deadline_count(summary->missed_deadline_count() + |
| data->jankTypeCount(kMissedDeadline)); |
| |
| bool creatingHistogram = false; |
| if (proto->histogram_size() == 0) { |
| proto->mutable_histogram()->Reserve(sHistogramSize); |
| creatingHistogram = true; |
| } else if (proto->histogram_size() != sHistogramSize) { |
| ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(), |
| sHistogramSize); |
| return false; |
| } |
| int index = 0; |
| bool hitMergeError = false; |
| data->histogramForEach([&](ProfileData::HistogramEntry entry) { |
| if (hitMergeError) return; |
| |
| protos::GraphicsStatsHistogramBucketProto* bucket; |
| if (creatingHistogram) { |
| bucket = proto->add_histogram(); |
| bucket->set_render_millis(entry.renderTimeMs); |
| } else { |
| bucket = proto->mutable_histogram(index); |
| if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) { |
| ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(), |
| entry.renderTimeMs); |
| hitMergeError = true; |
| return; |
| } |
| } |
| bucket->set_frame_count(bucket->frame_count() + entry.frameCount); |
| index++; |
| }); |
| if (hitMergeError) return false; |
| // fill in GPU frame time histogram |
| creatingHistogram = false; |
| if (proto->gpu_histogram_size() == 0) { |
| proto->mutable_gpu_histogram()->Reserve(sGPUHistogramSize); |
| creatingHistogram = true; |
| } else if (proto->gpu_histogram_size() != sGPUHistogramSize) { |
| ALOGE("GPU histogram size mismatch, proto is %d expected %d", proto->gpu_histogram_size(), |
| sGPUHistogramSize); |
| return false; |
| } |
| index = 0; |
| data->histogramGPUForEach([&](ProfileData::HistogramEntry entry) { |
| if (hitMergeError) return; |
| |
| protos::GraphicsStatsHistogramBucketProto* bucket; |
| if (creatingHistogram) { |
| bucket = proto->add_gpu_histogram(); |
| bucket->set_render_millis(entry.renderTimeMs); |
| } else { |
| bucket = proto->mutable_gpu_histogram(index); |
| if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) { |
| ALOGW("GPU frame time mistmatch %d vs. %u", bucket->render_millis(), |
| entry.renderTimeMs); |
| hitMergeError = true; |
| return; |
| } |
| } |
| bucket->set_frame_count(bucket->frame_count() + entry.frameCount); |
| index++; |
| }); |
| return !hitMergeError; |
| } |
| |
| static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) { |
| int32_t pos = percentile * proto->summary().total_frames() / 100; |
| int32_t remaining = proto->summary().total_frames() - pos; |
| for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) { |
| remaining -= it->frame_count(); |
| if (remaining <= 0) { |
| return it->render_millis(); |
| } |
| } |
| return 0; |
| } |
| |
| static int32_t findGPUPercentile(protos::GraphicsStatsProto* proto, int percentile) { |
| uint32_t totalGPUFrameCount = 0; // this is usually proto->summary().total_frames() - 3. |
| for (auto it = proto->gpu_histogram().rbegin(); it != proto->gpu_histogram().rend(); ++it) { |
| totalGPUFrameCount += it->frame_count(); |
| } |
| int32_t pos = percentile * totalGPUFrameCount / 100; |
| int32_t remaining = totalGPUFrameCount - pos; |
| for (auto it = proto->gpu_histogram().rbegin(); it != proto->gpu_histogram().rend(); ++it) { |
| remaining -= it->frame_count(); |
| if (remaining <= 0) { |
| return it->render_millis(); |
| } |
| } |
| return 0; |
| } |
| |
| void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) { |
| // This isn't a full validation, just enough that we can deref at will |
| if (proto->package_name().empty() || !proto->has_summary()) { |
| ALOGW("Skipping dump, invalid package_name() '%s' or summary %d", |
| proto->package_name().c_str(), proto->has_summary()); |
| return; |
| } |
| dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); |
| dprintf(fd, "\nVersion: %" PRId64, proto->version_code()); |
| dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start()); |
| dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end()); |
| auto summary = proto->summary(); |
| dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames()); |
| dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(), |
| (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f); |
| dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50)); |
| dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90)); |
| dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95)); |
| dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99)); |
| dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count()); |
| dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count()); |
| dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count()); |
| dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count()); |
| dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count()); |
| dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count()); |
| dprintf(fd, "\nHISTOGRAM:"); |
| for (const auto& it : proto->histogram()) { |
| dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count()); |
| } |
| dprintf(fd, "\n50th gpu percentile: %dms", findGPUPercentile(proto, 50)); |
| dprintf(fd, "\n90th gpu percentile: %dms", findGPUPercentile(proto, 90)); |
| dprintf(fd, "\n95th gpu percentile: %dms", findGPUPercentile(proto, 95)); |
| dprintf(fd, "\n99th gpu percentile: %dms", findGPUPercentile(proto, 99)); |
| dprintf(fd, "\nGPU HISTOGRAM:"); |
| for (const auto& it : proto->gpu_histogram()) { |
| dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count()); |
| } |
| dprintf(fd, "\n"); |
| } |
| |
| void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package, |
| int64_t versionCode, int64_t startTime, int64_t endTime, |
| const ProfileData* data) { |
| protos::GraphicsStatsProto statsProto; |
| if (!parseFromFile(path, &statsProto)) { |
| statsProto.Clear(); |
| } |
| if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { |
| return; |
| } |
| // Although we might not have read any data from the file, merging the existing data |
| // should always fully-initialize the proto |
| if (!statsProto.IsInitialized()) { |
| ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str()); |
| return; |
| } |
| if (statsProto.package_name().empty() || !statsProto.has_summary()) { |
| ALOGE("missing package_name() '%s' summary %d", statsProto.package_name().c_str(), |
| statsProto.has_summary()); |
| return; |
| } |
| int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660); |
| if (outFd <= 0) { |
| int err = errno; |
| ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err)); |
| return; |
| } |
| int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize); |
| if (wrote != sHeaderSize) { |
| int err = errno; |
| ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", path.c_str(), wrote, err, |
| strerror(err)); |
| close(outFd); |
| return; |
| } |
| { |
| FileOutputStreamLite output(outFd); |
| bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush(); |
| if (output.GetErrno() != 0) { |
| ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(), |
| output.GetErrno(), strerror(output.GetErrno())); |
| success = false; |
| } else if (!success) { |
| ALOGW("Serialize failed on '%s' unknown error", path.c_str()); |
| } |
| } |
| close(outFd); |
| } |
| |
| class GraphicsStatsService::Dump { |
| public: |
| Dump(int outFd, DumpType type) : mFd(outFd), mType(type) { |
| if (mFd == -1 && mType == DumpType::Protobuf) { |
| mType = DumpType::ProtobufStatsd; |
| } |
| } |
| int fd() { return mFd; } |
| DumpType type() { return mType; } |
| protos::GraphicsStatsServiceDumpProto& proto() { return mProto; } |
| void mergeStat(const protos::GraphicsStatsProto& stat); |
| void updateProto(); |
| |
| private: |
| // use package name and app version for a key |
| typedef std::pair<std::string, int64_t> DumpKey; |
| |
| std::map<DumpKey, protos::GraphicsStatsProto> mStats; |
| int mFd; |
| DumpType mType; |
| protos::GraphicsStatsServiceDumpProto mProto; |
| }; |
| |
| void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) { |
| auto dumpKey = std::make_pair(stat.package_name(), stat.version_code()); |
| auto findIt = mStats.find(dumpKey); |
| if (findIt == mStats.end()) { |
| mStats[dumpKey] = stat; |
| } else { |
| auto summary = findIt->second.mutable_summary(); |
| summary->set_total_frames(summary->total_frames() + stat.summary().total_frames()); |
| summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames()); |
| summary->set_missed_vsync_count(summary->missed_vsync_count() + |
| stat.summary().missed_vsync_count()); |
| summary->set_high_input_latency_count(summary->high_input_latency_count() + |
| stat.summary().high_input_latency_count()); |
| summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + |
| stat.summary().slow_ui_thread_count()); |
| summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + |
| stat.summary().slow_bitmap_upload_count()); |
| summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count()); |
| summary->set_missed_deadline_count(summary->missed_deadline_count() + |
| stat.summary().missed_deadline_count()); |
| for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) { |
| auto bucket = findIt->second.mutable_histogram(bucketIndex); |
| bucket->set_frame_count(bucket->frame_count() + |
| stat.histogram(bucketIndex).frame_count()); |
| } |
| for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size(); |
| bucketIndex++) { |
| auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex); |
| bucket->set_frame_count(bucket->frame_count() + |
| stat.gpu_histogram(bucketIndex).frame_count()); |
| } |
| findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start())); |
| findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end())); |
| } |
| } |
| |
| void GraphicsStatsService::Dump::updateProto() { |
| for (auto& stat : mStats) { |
| mProto.add_stats()->CopyFrom(stat.second); |
| } |
| } |
| |
| GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { |
| return new Dump(outFd, type); |
| } |
| |
| void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, |
| const std::string& package, int64_t versionCode, |
| int64_t startTime, int64_t endTime, const ProfileData* data) { |
| protos::GraphicsStatsProto statsProto; |
| if (!path.empty() && !parseFromFile(path, &statsProto)) { |
| statsProto.Clear(); |
| } |
| if (data && |
| !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { |
| return; |
| } |
| if (!statsProto.IsInitialized()) { |
| ALOGW("Failed to load profile data from path '%s' and data %p", |
| path.empty() ? "<empty>" : path.c_str(), data); |
| return; |
| } |
| if (dump->type() == DumpType::ProtobufStatsd) { |
| dump->mergeStat(statsProto); |
| } else if (dump->type() == DumpType::Protobuf) { |
| dump->proto().add_stats()->CopyFrom(statsProto); |
| } else { |
| dumpAsTextToFd(&statsProto, dump->fd()); |
| } |
| } |
| |
| void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { |
| protos::GraphicsStatsProto statsProto; |
| if (!parseFromFile(path, &statsProto)) { |
| return; |
| } |
| if (dump->type() == DumpType::ProtobufStatsd) { |
| dump->mergeStat(statsProto); |
| } else if (dump->type() == DumpType::Protobuf) { |
| dump->proto().add_stats()->CopyFrom(statsProto); |
| } else { |
| dumpAsTextToFd(&statsProto, dump->fd()); |
| } |
| } |
| |
| void GraphicsStatsService::finishDump(Dump* dump) { |
| if (dump->type() == DumpType::Protobuf) { |
| FileOutputStreamLite stream(dump->fd()); |
| dump->proto().SerializeToZeroCopyStream(&stream); |
| } |
| delete dump; |
| } |
| |
| using namespace google::protobuf; |
| |
| // Field ids taken from FrameTimingHistogram message in atoms.proto |
| #define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 |
| #define FRAME_COUNTS_FIELD_NUMBER 2 |
| |
| static void writeCpuHistogram(AStatsEvent* event, |
| const uirenderer::protos::GraphicsStatsProto& stat) { |
| util::ProtoOutputStream proto; |
| for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { |
| auto& bucket = stat.histogram(bucketIndex); |
| proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | |
| TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, |
| (int)bucket.render_millis()); |
| } |
| for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { |
| auto& bucket = stat.histogram(bucketIndex); |
| proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | |
| FRAME_COUNTS_FIELD_NUMBER /* field id */, |
| (long long)bucket.frame_count()); |
| } |
| std::vector<uint8_t> outVector; |
| proto.serializeToVector(&outVector); |
| AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); |
| } |
| |
| static void writeGpuHistogram(AStatsEvent* event, |
| const uirenderer::protos::GraphicsStatsProto& stat) { |
| util::ProtoOutputStream proto; |
| for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { |
| auto& bucket = stat.gpu_histogram(bucketIndex); |
| proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | |
| TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, |
| (int)bucket.render_millis()); |
| } |
| for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { |
| auto& bucket = stat.gpu_histogram(bucketIndex); |
| proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | |
| FRAME_COUNTS_FIELD_NUMBER /* field id */, |
| (long long)bucket.frame_count()); |
| } |
| std::vector<uint8_t> outVector; |
| proto.serializeToVector(&outVector); |
| AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); |
| } |
| |
| |
| void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data, |
| bool lastFullDay) { |
| dump->updateProto(); |
| auto& serviceDump = dump->proto(); |
| for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { |
| auto& stat = serviceDump.stats(stat_index); |
| AStatsEvent* event = AStatsEventList_addStatsEvent(data); |
| AStatsEvent_setAtomId(event, stats::GRAPHICS_STATS); |
| AStatsEvent_writeString(event, stat.package_name().c_str()); |
| AStatsEvent_writeInt64(event, (int64_t)stat.version_code()); |
| AStatsEvent_writeInt64(event, (int64_t)stat.stats_start()); |
| AStatsEvent_writeInt64(event, (int64_t)stat.stats_end()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.pipeline()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count()); |
| AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count()); |
| writeCpuHistogram(event, stat); |
| writeGpuHistogram(event, stat); |
| // TODO: fill in UI mainline module version, when the feature is available. |
| AStatsEvent_writeInt64(event, (int64_t)0); |
| AStatsEvent_writeBool(event, !lastFullDay); |
| AStatsEvent_build(event); |
| } |
| delete dump; |
| } |
| |
| |
| } /* namespace uirenderer */ |
| } /* namespace android */ |