blob: 7f8cb2d1f5778f8bb4f755aa133292b382d4363b [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"
Kweku Adams1856a4c2018-04-03 16:31:10 -070020#include "protos/graphicsstats.pb.h"
John Reckdf1742e2017-01-19 15:56:21 -080021
John Reck915883b2017-05-03 10:27:20 -070022#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
John Reckdf1742e2017-01-19 15:56:21 -080023#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>
John Reck1bcacfd2017-11-03 10:12:19 -070028#include <sys/mman.h>
Dan Albert110e0072017-10-11 12:41:26 -070029#include <sys/stat.h>
30#include <sys/types.h>
John Reckdf1742e2017-01-19 15:56:21 -080031#include <unistd.h>
32
33namespace android {
34namespace uirenderer {
35
36using namespace google::protobuf;
37
38constexpr int32_t sCurrentFileVersion = 1;
39constexpr int32_t sHeaderSize = 4;
40static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
41
John Reck7075c792017-07-05 14:03:43 -070042constexpr int sHistogramSize = ProfileData::HistogramSize();
John Reckdf1742e2017-01-19 15:56:21 -080043
Kweku Adams1856a4c2018-04-03 16:31:10 -070044static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
Dianne Hackborn73453e42017-12-11 16:30:36 -080045 const std::string& package, int64_t versionCode,
John Reck1bcacfd2017-11-03 10:12:19 -070046 int64_t startTime, int64_t endTime, const ProfileData* data);
Kweku Adams1856a4c2018-04-03 16:31:10 -070047static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
John Reckdf1742e2017-01-19 15:56:21 -080048
John Reck915883b2017-05-03 10:27:20 -070049class FileDescriptor {
50public:
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; }
John Reck1bcacfd2017-11-03 10:12:19 -070060
John Reck915883b2017-05-03 10:27:20 -070061private:
62 int mFd;
63};
64
65class FileOutputStreamLite : public io::ZeroCopyOutputStream {
66public:
67 FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
68 virtual ~FileOutputStreamLite() {}
69
70 int GetErrno() { return mCopyAdapter.mErrno; }
71
John Reck1bcacfd2017-11-03 10:12:19 -070072 virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
John Reck915883b2017-05-03 10:27:20 -070073
John Reck1bcacfd2017-11-03 10:12:19 -070074 virtual void BackUp(int count) override { mImpl.BackUp(count); }
John Reck915883b2017-05-03 10:27:20 -070075
John Reck1bcacfd2017-11-03 10:12:19 -070076 virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
John Reck915883b2017-05-03 10:27:20 -070077
John Reck1bcacfd2017-11-03 10:12:19 -070078 bool Flush() { return mImpl.Flush(); }
John Reck915883b2017-05-03 10:27:20 -070079
80private:
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) {
John Reck1bcacfd2017-11-03 10:12:19 -070091 ret = TEMP_FAILURE_RETRY(write(mFd, buffer, size));
John Reck915883b2017-05-03 10:27:20 -070092 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
John Reck1bcacfd2017-11-03 10:12:19 -0700106bool GraphicsStatsService::parseFromFile(const std::string& path,
Kweku Adams1856a4c2018-04-03 16:31:10 -0700107 protos::GraphicsStatsProto* output) {
John Reck915883b2017-05-03 10:27:20 -0700108 FileDescriptor fd{open(path.c_str(), O_RDONLY)};
109 if (!fd.valid()) {
John Reckdf1742e2017-01-19 15:56:21 -0800110 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 }
John Reck915883b2017-05-03 10:27:20 -0700118 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,
John Reck1bcacfd2017-11-03 10:12:19 -0700125 strerror(err), (int)sb.st_size);
John Reck915883b2017-05-03 10:27:20 -0700126 }
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);
John Reckdf1742e2017-01-19 15:56:21 -0800142 return false;
143 }
144
John Reck915883b2017-05-03 10:27:20 -0700145 void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
146 int dataSize = sb.st_size - sHeaderSize;
147 io::ArrayInputStream input{data, dataSize};
John Reckdf1742e2017-01-19 15:56:21 -0800148 bool success = output->ParseFromZeroCopyStream(&input);
John Reck915883b2017-05-03 10:27:20 -0700149 if (!success) {
John Reck1bcacfd2017-11-03 10:12:19 -0700150 ALOGW("Parse failed on '%s' error='%s'", path.c_str(),
151 output->InitializationErrorString().c_str());
John Reckdf1742e2017-01-19 15:56:21 -0800152 }
John Reckdf1742e2017-01-19 15:56:21 -0800153 return success;
154}
155
Kweku Adams1856a4c2018-04-03 16:31:10 -0700156bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
Dianne Hackborn73453e42017-12-11 16:30:36 -0800157 int64_t versionCode, int64_t startTime, int64_t endTime,
John Reck1bcacfd2017-11-03 10:12:19 -0700158 const ProfileData* data) {
John Reckdf1742e2017-01-19 15:56:21 -0800159 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();
John Reck7075c792017-07-05 14:03:43 -0700168 summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
169 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
John Reck1bcacfd2017-11-03 10:12:19 -0700170 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));
John Reck0e486472018-03-19 14:06:16 -0700179 summary->set_missed_deadline_count(summary->missed_deadline_count()
180 + data->jankTypeCount(kMissedDeadline));
John Reckdf1742e2017-01-19 15:56:21 -0800181
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) {
John Reck1bcacfd2017-11-03 10:12:19 -0700187 ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(),
188 sHistogramSize);
John Reck5206a872017-09-18 11:08:31 -0700189 return false;
John Reckdf1742e2017-01-19 15:56:21 -0800190 }
John Reck7075c792017-07-05 14:03:43 -0700191 int index = 0;
John Reck5206a872017-09-18 11:08:31 -0700192 bool hitMergeError = false;
John Reck7075c792017-07-05 14:03:43 -0700193 data->histogramForEach([&](ProfileData::HistogramEntry entry) {
John Reck5206a872017-09-18 11:08:31 -0700194 if (hitMergeError) return;
195
Kweku Adams1856a4c2018-04-03 16:31:10 -0700196 protos::GraphicsStatsHistogramBucketProto* bucket;
John Reckdf1742e2017-01-19 15:56:21 -0800197 if (creatingHistogram) {
198 bucket = proto->add_histogram();
John Reck7075c792017-07-05 14:03:43 -0700199 bucket->set_render_millis(entry.renderTimeMs);
John Reckdf1742e2017-01-19 15:56:21 -0800200 } else {
John Reck7075c792017-07-05 14:03:43 -0700201 bucket = proto->mutable_histogram(index);
John Reck5206a872017-09-18 11:08:31 -0700202 if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
John Reck1bcacfd2017-11-03 10:12:19 -0700203 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(),
204 entry.renderTimeMs);
John Reck5206a872017-09-18 11:08:31 -0700205 hitMergeError = true;
206 return;
207 }
John Reckdf1742e2017-01-19 15:56:21 -0800208 }
John Reck7075c792017-07-05 14:03:43 -0700209 bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
210 index++;
211 });
John Reck5206a872017-09-18 11:08:31 -0700212 return !hitMergeError;
John Reckdf1742e2017-01-19 15:56:21 -0800213}
214
Kweku Adams1856a4c2018-04-03 16:31:10 -0700215static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) {
John Reckdf1742e2017-01-19 15:56:21 -0800216 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
Kweku Adams1856a4c2018-04-03 16:31:10 -0700227void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) {
John Reckdf1742e2017-01-19 15:56:21 -0800228 // This isn't a full validation, just enough that we can deref at will
John Reck5206a872017-09-18 11:08:31 -0700229 if (proto->package_name().empty() || !proto->has_summary()) {
230 ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
John Reck1bcacfd2017-11-03 10:12:19 -0700231 proto->package_name().c_str(), proto->has_summary());
John Reck5206a872017-09-18 11:08:31 -0700232 return;
233 }
John Reckdf1742e2017-01-19 15:56:21 -0800234 dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
Dianne Hackborn73453e42017-12-11 16:30:36 -0800235 dprintf(fd, "\nVersion: %lld", proto->version_code());
John Reckdf1742e2017-01-19 15:56:21 -0800236 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(),
John Reck1bcacfd2017-11-03 10:12:19 -0700241 (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f);
John Reckdf1742e2017-01-19 15:56:21 -0800242 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());
John Reck0e486472018-03-19 14:06:16 -0700251 dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count());
John Reckdf1742e2017-01-19 15:56:21 -0800252 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
259void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
Dianne Hackborn73453e42017-12-11 16:30:36 -0800260 int64_t versionCode, int64_t startTime, int64_t endTime,
John Reck1bcacfd2017-11-03 10:12:19 -0700261 const ProfileData* data) {
Kweku Adams1856a4c2018-04-03 16:31:10 -0700262 protos::GraphicsStatsProto statsProto;
John Reckdf1742e2017-01-19 15:56:21 -0800263 if (!parseFromFile(path, &statsProto)) {
264 statsProto.Clear();
265 }
John Reck5206a872017-09-18 11:08:31 -0700266 if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
267 return;
268 }
John Reckdf1742e2017-01-19 15:56:21 -0800269 // Although we might not have read any data from the file, merging the existing data
270 // should always fully-initialize the proto
John Reck5206a872017-09-18 11:08:31 -0700271 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()) {
John Reck1bcacfd2017-11-03 10:12:19 -0700276 ALOGE("missing package_name() '%s' summary %d", statsProto.package_name().c_str(),
277 statsProto.has_summary());
John Reck5206a872017-09-18 11:08:31 -0700278 return;
279 }
John Reckdf1742e2017-01-19 15:56:21 -0800280 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;
John Reck1bcacfd2017-11-03 10:12:19 -0700289 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", path.c_str(), wrote, err,
290 strerror(err));
John Reckdf1742e2017-01-19 15:56:21 -0800291 close(outFd);
292 return;
293 }
294 {
John Reck915883b2017-05-03 10:27:20 -0700295 FileOutputStreamLite output(outFd);
John Reckdf1742e2017-01-19 15:56:21 -0800296 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
297 if (output.GetErrno() != 0) {
John Reck1bcacfd2017-11-03 10:12:19 -0700298 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(),
299 output.GetErrno(), strerror(output.GetErrno()));
John Reckdf1742e2017-01-19 15:56:21 -0800300 success = false;
301 } else if (!success) {
302 ALOGW("Serialize failed on '%s' unknown error", path.c_str());
303 }
304 }
305 close(outFd);
306}
307
308class GraphicsStatsService::Dump {
309public:
310 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
311 int fd() { return mFd; }
312 DumpType type() { return mType; }
Kweku Adams1856a4c2018-04-03 16:31:10 -0700313 protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
John Reck1bcacfd2017-11-03 10:12:19 -0700314
John Reckdf1742e2017-01-19 15:56:21 -0800315private:
316 int mFd;
317 DumpType mType;
Kweku Adams1856a4c2018-04-03 16:31:10 -0700318 protos::GraphicsStatsServiceDumpProto mProto;
John Reckdf1742e2017-01-19 15:56:21 -0800319};
320
321GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
322 return new Dump(outFd, type);
323}
324
John Reck1bcacfd2017-11-03 10:12:19 -0700325void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
Dianne Hackborn73453e42017-12-11 16:30:36 -0800326 const std::string& package, int64_t versionCode,
327 int64_t startTime, int64_t endTime, const ProfileData* data) {
Kweku Adams1856a4c2018-04-03 16:31:10 -0700328 protos::GraphicsStatsProto statsProto;
John Reckdf1742e2017-01-19 15:56:21 -0800329 if (!path.empty() && !parseFromFile(path, &statsProto)) {
330 statsProto.Clear();
331 }
John Reck1bcacfd2017-11-03 10:12:19 -0700332 if (data &&
333 !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
John Reck5206a872017-09-18 11:08:31 -0700334 return;
John Reckdf1742e2017-01-19 15:56:21 -0800335 }
336 if (!statsProto.IsInitialized()) {
337 ALOGW("Failed to load profile data from path '%s' and data %p",
John Reck1bcacfd2017-11-03 10:12:19 -0700338 path.empty() ? "<empty>" : path.c_str(), data);
John Reckdf1742e2017-01-19 15:56:21 -0800339 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
349void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
Kweku Adams1856a4c2018-04-03 16:31:10 -0700350 protos::GraphicsStatsProto statsProto;
John Reckdf1742e2017-01-19 15:56:21 -0800351 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
361void GraphicsStatsService::finishDump(Dump* dump) {
362 if (dump->type() == DumpType::Protobuf) {
John Reck915883b2017-05-03 10:27:20 -0700363 FileOutputStreamLite stream(dump->fd());
John Reckdf1742e2017-01-19 15:56:21 -0800364 dump->proto().SerializeToZeroCopyStream(&stream);
365 }
366 delete dump;
367}
368
369} /* namespace uirenderer */
Dan Albert110e0072017-10-11 12:41:26 -0700370} /* namespace android */