GraphicsStatsService.cpp revision 41c08f49b467e5e8aa291ec4a704a301bd27a6dc
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 "JankTracker.h" 20 21#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h> 22#include <google/protobuf/io/zero_copy_stream_impl_lite.h> 23#include <log/log.h> 24 25#include <errno.h> 26#include <fcntl.h> 27#include <inttypes.h> 28#include <sys/stat.h> 29#include <sys/types.h> 30#include <unistd.h> 31#include <sys/mman.h> 32 33namespace android { 34namespace uirenderer { 35 36using namespace google::protobuf; 37 38constexpr int32_t sCurrentFileVersion = 1; 39constexpr int32_t sHeaderSize = 4; 40static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong"); 41 42constexpr int sHistogramSize = 43 std::tuple_size<decltype(ProfileData::frameCounts)>::value + 44 std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value; 45 46static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, 47 const std::string& package, int versionCode, int64_t startTime, int64_t endTime, 48 const ProfileData* data); 49static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd); 50 51class FileDescriptor { 52public: 53 FileDescriptor(int fd) : mFd(fd) {} 54 ~FileDescriptor() { 55 if (mFd != -1) { 56 close(mFd); 57 mFd = -1; 58 } 59 } 60 bool valid() { return mFd != -1; } 61 operator int() { return mFd; } 62private: 63 int mFd; 64}; 65 66class FileOutputStreamLite : public io::ZeroCopyOutputStream { 67public: 68 FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {} 69 virtual ~FileOutputStreamLite() {} 70 71 int GetErrno() { return mCopyAdapter.mErrno; } 72 73 virtual bool Next(void** data, int* size) override { 74 return mImpl.Next(data, size); 75 } 76 77 virtual void BackUp(int count) override { 78 mImpl.BackUp(count); 79 } 80 81 virtual int64 ByteCount() const override { 82 return mImpl.ByteCount(); 83 } 84 85 bool Flush() { 86 return mImpl.Flush(); 87 } 88 89private: 90 struct FDAdapter : public io::CopyingOutputStream { 91 int mFd; 92 int mErrno = 0; 93 94 FDAdapter(int fd) : mFd(fd) {} 95 virtual ~FDAdapter() {} 96 97 virtual bool Write(const void* buffer, int size) override { 98 int ret; 99 while (size) { 100 ret = TEMP_FAILURE_RETRY( write(mFd, buffer, size) ); 101 if (ret <= 0) { 102 mErrno = errno; 103 return false; 104 } 105 size -= ret; 106 } 107 return true; 108 } 109 }; 110 111 FileOutputStreamLite::FDAdapter mCopyAdapter; 112 io::CopyingOutputStreamAdaptor mImpl; 113}; 114 115bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) { 116 117 FileDescriptor fd{open(path.c_str(), O_RDONLY)}; 118 if (!fd.valid()) { 119 int err = errno; 120 // The file not existing is normal for addToDump(), so only log if 121 // we get an unexpected error 122 if (err != ENOENT) { 123 ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err)); 124 } 125 return false; 126 } 127 struct stat sb; 128 if (fstat(fd, &sb) || sb.st_size < sHeaderSize) { 129 int err = errno; 130 // The file not existing is normal for addToDump(), so only log if 131 // we get an unexpected error 132 if (err != ENOENT) { 133 ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err, 134 strerror(err), (int) sb.st_size); 135 } 136 return false; 137 } 138 void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); 139 if (!addr) { 140 int err = errno; 141 // The file not existing is normal for addToDump(), so only log if 142 // we get an unexpected error 143 if (err != ENOENT) { 144 ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err)); 145 } 146 return false; 147 } 148 uint32_t file_version = *reinterpret_cast<uint32_t*>(addr); 149 if (file_version != sCurrentFileVersion) { 150 ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version); 151 return false; 152 } 153 154 void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize; 155 int dataSize = sb.st_size - sHeaderSize; 156 io::ArrayInputStream input{data, dataSize}; 157 bool success = output->ParseFromZeroCopyStream(&input); 158 if (!success) { 159 ALOGW("Parse failed on '%s' error='%s'", 160 path.c_str(), output->InitializationErrorString().c_str()); 161 } 162 return success; 163} 164 165void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, 166 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { 167 if (proto->stats_start() == 0 || proto->stats_start() > startTime) { 168 proto->set_stats_start(startTime); 169 } 170 if (proto->stats_end() == 0 || proto->stats_end() < endTime) { 171 proto->set_stats_end(endTime); 172 } 173 proto->set_package_name(package); 174 proto->set_version_code(versionCode); 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( 179 summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]); 180 summary->set_high_input_latency_count( 181 summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]); 182 summary->set_slow_ui_thread_count( 183 summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]); 184 summary->set_slow_bitmap_upload_count( 185 summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]); 186 summary->set_slow_draw_count( 187 summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]); 188 189 bool creatingHistogram = false; 190 if (proto->histogram_size() == 0) { 191 proto->mutable_histogram()->Reserve(sHistogramSize); 192 creatingHistogram = true; 193 } else if (proto->histogram_size() != sHistogramSize) { 194 LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d", 195 proto->histogram_size(), sHistogramSize); 196 } 197 for (size_t i = 0; i < data->frameCounts.size(); i++) { 198 service::GraphicsStatsHistogramBucketProto* bucket; 199 int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i); 200 if (creatingHistogram) { 201 bucket = proto->add_histogram(); 202 bucket->set_render_millis(renderTime); 203 } else { 204 bucket = proto->mutable_histogram(i); 205 LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, 206 "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); 207 } 208 bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]); 209 } 210 for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { 211 service::GraphicsStatsHistogramBucketProto* bucket; 212 int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i); 213 if (creatingHistogram) { 214 bucket = proto->add_histogram(); 215 bucket->set_render_millis(renderTime); 216 } else { 217 constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value; 218 bucket = proto->mutable_histogram(offset + i); 219 LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, 220 "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); 221 } 222 bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]); 223 } 224} 225 226static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) { 227 int32_t pos = percentile * proto->summary().total_frames() / 100; 228 int32_t remaining = proto->summary().total_frames() - pos; 229 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) { 230 remaining -= it->frame_count(); 231 if (remaining <= 0) { 232 return it->render_millis(); 233 } 234 } 235 return 0; 236} 237 238void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) { 239 // This isn't a full validation, just enough that we can deref at will 240 LOG_ALWAYS_FATAL_IF(proto->package_name().empty() 241 || !proto->has_summary(), "package_name() '%s' summary %d", 242 proto->package_name().c_str(), proto->has_summary()); 243 dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); 244 dprintf(fd, "\nVersion: %d", proto->version_code()); 245 dprintf(fd, "\nStats since: %lldns", proto->stats_start()); 246 dprintf(fd, "\nStats end: %lldns", proto->stats_end()); 247 auto summary = proto->summary(); 248 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames()); 249 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(), 250 (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f); 251 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50)); 252 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90)); 253 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95)); 254 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99)); 255 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count()); 256 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count()); 257 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count()); 258 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count()); 259 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count()); 260 dprintf(fd, "\nHISTOGRAM:"); 261 for (const auto& it : proto->histogram()) { 262 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count()); 263 } 264 dprintf(fd, "\n"); 265} 266 267void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package, 268 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { 269 service::GraphicsStatsProto statsProto; 270 if (!parseFromFile(path, &statsProto)) { 271 statsProto.Clear(); 272 } 273 mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); 274 // Although we might not have read any data from the file, merging the existing data 275 // should always fully-initialize the proto 276 LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s", 277 statsProto.InitializationErrorString().c_str()); 278 LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty() 279 || !statsProto.has_summary(), "package_name() '%s' summary %d", 280 statsProto.package_name().c_str(), statsProto.has_summary()); 281 int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660); 282 if (outFd <= 0) { 283 int err = errno; 284 ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err)); 285 return; 286 } 287 int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize); 288 if (wrote != sHeaderSize) { 289 int err = errno; 290 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", 291 path.c_str(), wrote, err, strerror(err)); 292 close(outFd); 293 return; 294 } 295 { 296 FileOutputStreamLite output(outFd); 297 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush(); 298 if (output.GetErrno() != 0) { 299 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", 300 outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno())); 301 success = false; 302 } else if (!success) { 303 ALOGW("Serialize failed on '%s' unknown error", path.c_str()); 304 } 305 } 306 close(outFd); 307} 308 309class GraphicsStatsService::Dump { 310public: 311 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} 312 int fd() { return mFd; } 313 DumpType type() { return mType; } 314 service::GraphicsStatsServiceDumpProto& proto() { return mProto; } 315private: 316 int mFd; 317 DumpType mType; 318 service::GraphicsStatsServiceDumpProto mProto; 319}; 320 321GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { 322 return new Dump(outFd, type); 323} 324 325void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package, 326 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { 327 service::GraphicsStatsProto statsProto; 328 if (!path.empty() && !parseFromFile(path, &statsProto)) { 329 statsProto.Clear(); 330 } 331 if (data) { 332 mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); 333 } 334 if (!statsProto.IsInitialized()) { 335 ALOGW("Failed to load profile data from path '%s' and data %p", 336 path.empty() ? "<empty>" : path.c_str(), data); 337 return; 338 } 339 340 if (dump->type() == DumpType::Protobuf) { 341 dump->proto().add_stats()->CopyFrom(statsProto); 342 } else { 343 dumpAsTextToFd(&statsProto, dump->fd()); 344 } 345} 346 347void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { 348 service::GraphicsStatsProto statsProto; 349 if (!parseFromFile(path, &statsProto)) { 350 return; 351 } 352 if (dump->type() == DumpType::Protobuf) { 353 dump->proto().add_stats()->CopyFrom(statsProto); 354 } else { 355 dumpAsTextToFd(&statsProto, dump->fd()); 356 } 357} 358 359void GraphicsStatsService::finishDump(Dump* dump) { 360 if (dump->type() == DumpType::Protobuf) { 361 FileOutputStreamLite stream(dump->fd()); 362 dump->proto().SerializeToZeroCopyStream(&stream); 363 } 364 delete dump; 365} 366 367} /* namespace uirenderer */ 368} /* namespace android */ 369