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 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; } 60 61 private: 62 int mFd; 63 }; 64 65 class FileOutputStreamLite : public io::ZeroCopyOutputStream { 66 public: 67 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 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) { 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 return false; 143 } 144 145 void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize; 146 int dataSize = sb.st_size - sHeaderSize; 147 io::ArrayInputStream input{data, dataSize}; 148 bool success = output->ParseFromZeroCopyStream(&input); 149 if (!success) { 150 ALOGW("Parse failed on '%s' error='%s'", path.c_str(), 151 output->InitializationErrorString().c_str()); 152 } 153 return success; 154 } 155 156 bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, 157 int64_t versionCode, int64_t startTime, int64_t endTime, 158 const ProfileData* data) { 159 if (proto->stats_start() == 0 || proto->stats_start() > startTime) { 160 proto->set_stats_start(startTime); 161 } 162 if (proto->stats_end() == 0 || proto->stats_end() < endTime) { 163 proto->set_stats_end(endTime); 164 } 165 proto->set_package_name(package); 166 proto->set_version_code(versionCode); 167 auto summary = proto->mutable_summary(); 168 summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); 169 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); 170 summary->set_missed_vsync_count(summary->missed_vsync_count() + 171 data->jankTypeCount(kMissedVsync)); 172 summary->set_high_input_latency_count(summary->high_input_latency_count() + 173 data->jankTypeCount(kHighInputLatency)); 174 summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + 175 data->jankTypeCount(kSlowUI)); 176 summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + 177 data->jankTypeCount(kSlowSync)); 178 summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); 179 summary->set_missed_deadline_count(summary->missed_deadline_count() 180 + data->jankTypeCount(kMissedDeadline)); 181 182 bool creatingHistogram = false; 183 if (proto->histogram_size() == 0) { 184 proto->mutable_histogram()->Reserve(sHistogramSize); 185 creatingHistogram = true; 186 } else if (proto->histogram_size() != sHistogramSize) { 187 ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(), 188 sHistogramSize); 189 return false; 190 } 191 int index = 0; 192 bool hitMergeError = false; 193 data->histogramForEach([&](ProfileData::HistogramEntry entry) { 194 if (hitMergeError) return; 195 196 protos::GraphicsStatsHistogramBucketProto* bucket; 197 if (creatingHistogram) { 198 bucket = proto->add_histogram(); 199 bucket->set_render_millis(entry.renderTimeMs); 200 } else { 201 bucket = proto->mutable_histogram(index); 202 if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) { 203 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(), 204 entry.renderTimeMs); 205 hitMergeError = true; 206 return; 207 } 208 } 209 bucket->set_frame_count(bucket->frame_count() + entry.frameCount); 210 index++; 211 }); 212 return !hitMergeError; 213 } 214 215 static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) { 216 int32_t pos = percentile * proto->summary().total_frames() / 100; 217 int32_t remaining = proto->summary().total_frames() - pos; 218 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) { 219 remaining -= it->frame_count(); 220 if (remaining <= 0) { 221 return it->render_millis(); 222 } 223 } 224 return 0; 225 } 226 227 void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) { 228 // This isn't a full validation, just enough that we can deref at will 229 if (proto->package_name().empty() || !proto->has_summary()) { 230 ALOGW("Skipping dump, invalid package_name() '%s' or summary %d", 231 proto->package_name().c_str(), proto->has_summary()); 232 return; 233 } 234 dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); 235 dprintf(fd, "\nVersion: %lld", proto->version_code()); 236 dprintf(fd, "\nStats since: %lldns", proto->stats_start()); 237 dprintf(fd, "\nStats end: %lldns", proto->stats_end()); 238 auto summary = proto->summary(); 239 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames()); 240 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(), 241 (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f); 242 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50)); 243 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90)); 244 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95)); 245 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99)); 246 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count()); 247 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count()); 248 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count()); 249 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count()); 250 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count()); 251 dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count()); 252 dprintf(fd, "\nHISTOGRAM:"); 253 for (const auto& it : proto->histogram()) { 254 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count()); 255 } 256 dprintf(fd, "\n"); 257 } 258 259 void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package, 260 int64_t versionCode, int64_t startTime, int64_t endTime, 261 const ProfileData* data) { 262 protos::GraphicsStatsProto statsProto; 263 if (!parseFromFile(path, &statsProto)) { 264 statsProto.Clear(); 265 } 266 if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { 267 return; 268 } 269 // Although we might not have read any data from the file, merging the existing data 270 // should always fully-initialize the proto 271 if (!statsProto.IsInitialized()) { 272 ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str()); 273 return; 274 } 275 if (statsProto.package_name().empty() || !statsProto.has_summary()) { 276 ALOGE("missing package_name() '%s' summary %d", statsProto.package_name().c_str(), 277 statsProto.has_summary()); 278 return; 279 } 280 int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660); 281 if (outFd <= 0) { 282 int err = errno; 283 ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err)); 284 return; 285 } 286 int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize); 287 if (wrote != sHeaderSize) { 288 int err = errno; 289 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", path.c_str(), wrote, err, 290 strerror(err)); 291 close(outFd); 292 return; 293 } 294 { 295 FileOutputStreamLite output(outFd); 296 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush(); 297 if (output.GetErrno() != 0) { 298 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(), 299 output.GetErrno(), strerror(output.GetErrno())); 300 success = false; 301 } else if (!success) { 302 ALOGW("Serialize failed on '%s' unknown error", path.c_str()); 303 } 304 } 305 close(outFd); 306 } 307 308 class GraphicsStatsService::Dump { 309 public: 310 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} 311 int fd() { return mFd; } 312 DumpType type() { return mType; } 313 protos::GraphicsStatsServiceDumpProto& proto() { return mProto; } 314 315 private: 316 int mFd; 317 DumpType mType; 318 protos::GraphicsStatsServiceDumpProto mProto; 319 }; 320 321 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { 322 return new Dump(outFd, type); 323 } 324 325 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, 326 const std::string& package, int64_t versionCode, 327 int64_t startTime, int64_t endTime, const ProfileData* data) { 328 protos::GraphicsStatsProto statsProto; 329 if (!path.empty() && !parseFromFile(path, &statsProto)) { 330 statsProto.Clear(); 331 } 332 if (data && 333 !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { 334 return; 335 } 336 if (!statsProto.IsInitialized()) { 337 ALOGW("Failed to load profile data from path '%s' and data %p", 338 path.empty() ? "<empty>" : path.c_str(), data); 339 return; 340 } 341 342 if (dump->type() == DumpType::Protobuf) { 343 dump->proto().add_stats()->CopyFrom(statsProto); 344 } else { 345 dumpAsTextToFd(&statsProto, dump->fd()); 346 } 347 } 348 349 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { 350 protos::GraphicsStatsProto statsProto; 351 if (!parseFromFile(path, &statsProto)) { 352 return; 353 } 354 if (dump->type() == DumpType::Protobuf) { 355 dump->proto().add_stats()->CopyFrom(statsProto); 356 } else { 357 dumpAsTextToFd(&statsProto, dump->fd()); 358 } 359 } 360 361 void GraphicsStatsService::finishDump(Dump* dump) { 362 if (dump->type() == DumpType::Protobuf) { 363 FileOutputStreamLite stream(dump->fd()); 364 dump->proto().SerializeToZeroCopyStream(&stream); 365 } 366 delete dump; 367 } 368 369 } /* namespace uirenderer */ 370 } /* namespace android */ 371