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
parseFromFile(const std::string & path,service::GraphicsStatsProto * output)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
mergeProfileDataIntoProto(service::GraphicsStatsProto * proto,const std::string & package,int versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)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
findPercentile(service::GraphicsStatsProto * proto,int percentile)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
dumpAsTextToFd(service::GraphicsStatsProto * proto,int fd)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
saveBuffer(const std::string & path,const std::string & package,int versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)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:
Dump(int outFd,DumpType type)230 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
fd()231 int fd() { return mFd; }
type()232 DumpType type() { return mType; }
proto()233 service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
234 private:
235 int mFd;
236 DumpType mType;
237 service::GraphicsStatsServiceDumpProto mProto;
238 };
239
createDump(int outFd,DumpType type)240 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
241 return new Dump(outFd, type);
242 }
243
addToDump(Dump * dump,const std::string & path,const std::string & package,int versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)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
addToDump(Dump * dump,const std::string & path)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
finishDump(Dump * dump)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 */