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