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