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.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 31 namespace android { 32 namespace uirenderer { 33 34 using namespace google::protobuf; 35 36 constexpr int32_t sCurrentFileVersion = 1; 37 constexpr int32_t sHeaderSize = 4; 38 static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong"); 39 40 constexpr int sHistogramSize = 41 std::tuple_size<decltype(ProfileData::frameCounts)>::value + 42 std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value; 43 44 static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, 45 const std::string& package, int versionCode, int64_t startTime, int64_t endTime, 46 const ProfileData* data); 47 static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd); 48 49 bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) { 50 51 int fd = open(path.c_str(), O_RDONLY); 52 if (fd == -1) { 53 int err = errno; 54 // The file not existing is normal for addToDump(), so only log if 55 // we get an unexpected error 56 if (err != ENOENT) { 57 ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err)); 58 } 59 return false; 60 } 61 uint32_t file_version; 62 ssize_t bytesRead = read(fd, &file_version, sHeaderSize); 63 if (bytesRead != sHeaderSize || file_version != sCurrentFileVersion) { 64 ALOGW("Failed to read '%s', bytesRead=%zd file_version=%d", path.c_str(), bytesRead, 65 file_version); 66 close(fd); 67 return false; 68 } 69 70 io::FileInputStream input(fd); 71 bool success = output->ParseFromZeroCopyStream(&input); 72 if (input.GetErrno() != 0) { 73 ALOGW("Error reading from fd=%d, path='%s' err=%d (%s)", 74 fd, path.c_str(), input.GetErrno(), strerror(input.GetErrno())); 75 success = false; 76 } else if (!success) { 77 ALOGW("Parse failed on '%s' error='%s'", 78 path.c_str(), output->InitializationErrorString().c_str()); 79 } 80 close(fd); 81 return success; 82 } 83 84 void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, 85 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { 86 if (proto->stats_start() == 0 || proto->stats_start() > startTime) { 87 proto->set_stats_start(startTime); 88 } 89 if (proto->stats_end() == 0 || proto->stats_end() < endTime) { 90 proto->set_stats_end(endTime); 91 } 92 proto->set_package_name(package); 93 proto->set_version_code(versionCode); 94 auto summary = proto->mutable_summary(); 95 summary->set_total_frames(summary->total_frames() + data->totalFrameCount); 96 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount); 97 summary->set_missed_vsync_count( 98 summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]); 99 summary->set_high_input_latency_count( 100 summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]); 101 summary->set_slow_ui_thread_count( 102 summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]); 103 summary->set_slow_bitmap_upload_count( 104 summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]); 105 summary->set_slow_draw_count( 106 summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]); 107 108 bool creatingHistogram = false; 109 if (proto->histogram_size() == 0) { 110 proto->mutable_histogram()->Reserve(sHistogramSize); 111 creatingHistogram = true; 112 } else if (proto->histogram_size() != sHistogramSize) { 113 LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d", 114 proto->histogram_size(), sHistogramSize); 115 } 116 for (size_t i = 0; i < data->frameCounts.size(); i++) { 117 service::GraphicsStatsHistogramBucketProto* bucket; 118 int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i); 119 if (creatingHistogram) { 120 bucket = proto->add_histogram(); 121 bucket->set_render_millis(renderTime); 122 } else { 123 bucket = proto->mutable_histogram(i); 124 LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, 125 "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); 126 } 127 bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]); 128 } 129 for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { 130 service::GraphicsStatsHistogramBucketProto* bucket; 131 int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i); 132 if (creatingHistogram) { 133 bucket = proto->add_histogram(); 134 bucket->set_render_millis(renderTime); 135 } else { 136 constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value; 137 bucket = proto->mutable_histogram(offset + i); 138 LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, 139 "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); 140 } 141 bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]); 142 } 143 } 144 145 static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) { 146 int32_t pos = percentile * proto->summary().total_frames() / 100; 147 int32_t remaining = proto->summary().total_frames() - pos; 148 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) { 149 remaining -= it->frame_count(); 150 if (remaining <= 0) { 151 return it->render_millis(); 152 } 153 } 154 return 0; 155 } 156 157 void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) { 158 // This isn't a full validation, just enough that we can deref at will 159 LOG_ALWAYS_FATAL_IF(proto->package_name().empty() 160 || !proto->has_summary(), "package_name() '%s' summary %d", 161 proto->package_name().c_str(), proto->has_summary()); 162 dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); 163 dprintf(fd, "\nVersion: %d", proto->version_code()); 164 dprintf(fd, "\nStats since: %lldns", proto->stats_start()); 165 dprintf(fd, "\nStats end: %lldns", proto->stats_end()); 166 auto summary = proto->summary(); 167 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames()); 168 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(), 169 (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f); 170 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50)); 171 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90)); 172 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95)); 173 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99)); 174 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count()); 175 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count()); 176 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count()); 177 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count()); 178 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count()); 179 dprintf(fd, "\nHISTOGRAM:"); 180 for (const auto& it : proto->histogram()) { 181 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count()); 182 } 183 dprintf(fd, "\n"); 184 } 185 186 void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package, 187 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { 188 service::GraphicsStatsProto statsProto; 189 if (!parseFromFile(path, &statsProto)) { 190 statsProto.Clear(); 191 } 192 mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); 193 // Although we might not have read any data from the file, merging the existing data 194 // should always fully-initialize the proto 195 LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s", 196 statsProto.InitializationErrorString().c_str()); 197 LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty() 198 || !statsProto.has_summary(), "package_name() '%s' summary %d", 199 statsProto.package_name().c_str(), statsProto.has_summary()); 200 int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660); 201 if (outFd <= 0) { 202 int err = errno; 203 ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err)); 204 return; 205 } 206 int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize); 207 if (wrote != sHeaderSize) { 208 int err = errno; 209 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", 210 path.c_str(), wrote, err, strerror(err)); 211 close(outFd); 212 return; 213 } 214 { 215 io::FileOutputStream output(outFd); 216 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush(); 217 if (output.GetErrno() != 0) { 218 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", 219 outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno())); 220 success = false; 221 } else if (!success) { 222 ALOGW("Serialize failed on '%s' unknown error", path.c_str()); 223 } 224 } 225 close(outFd); 226 } 227 228 class GraphicsStatsService::Dump { 229 public: 230 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} 231 int fd() { return mFd; } 232 DumpType type() { return mType; } 233 service::GraphicsStatsServiceDumpProto& proto() { return mProto; } 234 private: 235 int mFd; 236 DumpType mType; 237 service::GraphicsStatsServiceDumpProto mProto; 238 }; 239 240 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { 241 return new Dump(outFd, type); 242 } 243 244 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package, 245 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { 246 service::GraphicsStatsProto statsProto; 247 if (!path.empty() && !parseFromFile(path, &statsProto)) { 248 statsProto.Clear(); 249 } 250 if (data) { 251 mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); 252 } 253 if (!statsProto.IsInitialized()) { 254 ALOGW("Failed to load profile data from path '%s' and data %p", 255 path.empty() ? "<empty>" : path.c_str(), data); 256 return; 257 } 258 259 if (dump->type() == DumpType::Protobuf) { 260 dump->proto().add_stats()->CopyFrom(statsProto); 261 } else { 262 dumpAsTextToFd(&statsProto, dump->fd()); 263 } 264 } 265 266 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { 267 service::GraphicsStatsProto statsProto; 268 if (!parseFromFile(path, &statsProto)) { 269 return; 270 } 271 if (dump->type() == DumpType::Protobuf) { 272 dump->proto().add_stats()->CopyFrom(statsProto); 273 } else { 274 dumpAsTextToFd(&statsProto, dump->fd()); 275 } 276 } 277 278 void GraphicsStatsService::finishDump(Dump* dump) { 279 if (dump->type() == DumpType::Protobuf) { 280 io::FileOutputStream stream(dump->fd()); 281 dump->proto().SerializeToZeroCopyStream(&stream); 282 } 283 delete dump; 284 } 285 286 } /* namespace uirenderer */ 287 } /* namespace android */