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