blob: f7a90b0a65ec756b8b41c9115a86577f0d587a77 [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>
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>
28#include <sys/stat.h>
29#include <sys/types.h>
John Reckdf1742e2017-01-19 15:56:21 -080030#include <unistd.h>
John Reck915883b2017-05-03 10:27:20 -070031#include <sys/mman.h>
John Reckdf1742e2017-01-19 15:56:21 -080032
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
John Reck5206a872017-09-18 11:08:31 -070044static bool mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
John Reckdf1742e2017-01-19 15:56:21 -080045 const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
46 const ProfileData* data);
47static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
48
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; }
60private:
61 int mFd;
62};
63
64class FileOutputStreamLite : public io::ZeroCopyOutputStream {
65public:
66 FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
67 virtual ~FileOutputStreamLite() {}
68
69 int GetErrno() { return mCopyAdapter.mErrno; }
70
71 virtual bool Next(void** data, int* size) override {
72 return mImpl.Next(data, size);
73 }
74
75 virtual void BackUp(int count) override {
76 mImpl.BackUp(count);
77 }
78
79 virtual int64 ByteCount() const override {
80 return mImpl.ByteCount();
81 }
82
83 bool Flush() {
84 return mImpl.Flush();
85 }
86
87private:
88 struct FDAdapter : public io::CopyingOutputStream {
89 int mFd;
90 int mErrno = 0;
91
92 FDAdapter(int fd) : mFd(fd) {}
93 virtual ~FDAdapter() {}
94
95 virtual bool Write(const void* buffer, int size) override {
96 int ret;
97 while (size) {
98 ret = TEMP_FAILURE_RETRY( write(mFd, buffer, size) );
99 if (ret <= 0) {
100 mErrno = errno;
101 return false;
102 }
103 size -= ret;
104 }
105 return true;
106 }
107 };
108
109 FileOutputStreamLite::FDAdapter mCopyAdapter;
110 io::CopyingOutputStreamAdaptor mImpl;
111};
112
John Reckdf1742e2017-01-19 15:56:21 -0800113bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
114
John Reck915883b2017-05-03 10:27:20 -0700115 FileDescriptor fd{open(path.c_str(), O_RDONLY)};
116 if (!fd.valid()) {
John Reckdf1742e2017-01-19 15:56:21 -0800117 int err = errno;
118 // The file not existing is normal for addToDump(), so only log if
119 // we get an unexpected error
120 if (err != ENOENT) {
121 ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
122 }
123 return false;
124 }
John Reck915883b2017-05-03 10:27:20 -0700125 struct stat sb;
126 if (fstat(fd, &sb) || sb.st_size < sHeaderSize) {
127 int err = errno;
128 // The file not existing is normal for addToDump(), so only log if
129 // we get an unexpected error
130 if (err != ENOENT) {
131 ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err,
132 strerror(err), (int) sb.st_size);
133 }
134 return false;
135 }
136 void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
137 if (!addr) {
138 int err = errno;
139 // The file not existing is normal for addToDump(), so only log if
140 // we get an unexpected error
141 if (err != ENOENT) {
142 ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
143 }
144 return false;
145 }
146 uint32_t file_version = *reinterpret_cast<uint32_t*>(addr);
147 if (file_version != sCurrentFileVersion) {
148 ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version);
John Reckdf1742e2017-01-19 15:56:21 -0800149 return false;
150 }
151
John Reck915883b2017-05-03 10:27:20 -0700152 void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
153 int dataSize = sb.st_size - sHeaderSize;
154 io::ArrayInputStream input{data, dataSize};
John Reckdf1742e2017-01-19 15:56:21 -0800155 bool success = output->ParseFromZeroCopyStream(&input);
John Reck915883b2017-05-03 10:27:20 -0700156 if (!success) {
John Reckdf1742e2017-01-19 15:56:21 -0800157 ALOGW("Parse failed on '%s' error='%s'",
158 path.c_str(), output->InitializationErrorString().c_str());
159 }
John Reckdf1742e2017-01-19 15:56:21 -0800160 return success;
161}
162
John Reck5206a872017-09-18 11:08:31 -0700163bool mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package,
John Reckdf1742e2017-01-19 15:56:21 -0800164 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
165 if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
166 proto->set_stats_start(startTime);
167 }
168 if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
169 proto->set_stats_end(endTime);
170 }
171 proto->set_package_name(package);
172 proto->set_version_code(versionCode);
173 auto summary = proto->mutable_summary();
John Reck7075c792017-07-05 14:03:43 -0700174 summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
175 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
John Reckdf1742e2017-01-19 15:56:21 -0800176 summary->set_missed_vsync_count(
John Reck7075c792017-07-05 14:03:43 -0700177 summary->missed_vsync_count() + data->jankTypeCount(kMissedVsync));
John Reckdf1742e2017-01-19 15:56:21 -0800178 summary->set_high_input_latency_count(
John Reck7075c792017-07-05 14:03:43 -0700179 summary->high_input_latency_count() + data->jankTypeCount(kHighInputLatency));
John Reckdf1742e2017-01-19 15:56:21 -0800180 summary->set_slow_ui_thread_count(
John Reck7075c792017-07-05 14:03:43 -0700181 summary->slow_ui_thread_count() + data->jankTypeCount(kSlowUI));
John Reckdf1742e2017-01-19 15:56:21 -0800182 summary->set_slow_bitmap_upload_count(
John Reck7075c792017-07-05 14:03:43 -0700183 summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync));
John Reckdf1742e2017-01-19 15:56:21 -0800184 summary->set_slow_draw_count(
John Reck7075c792017-07-05 14:03:43 -0700185 summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
John Reckdf1742e2017-01-19 15:56:21 -0800186
187 bool creatingHistogram = false;
188 if (proto->histogram_size() == 0) {
189 proto->mutable_histogram()->Reserve(sHistogramSize);
190 creatingHistogram = true;
191 } else if (proto->histogram_size() != sHistogramSize) {
John Reck5206a872017-09-18 11:08:31 -0700192 ALOGE("Histogram size mismatch, proto is %d expected %d",
John Reckdf1742e2017-01-19 15:56:21 -0800193 proto->histogram_size(), sHistogramSize);
John Reck5206a872017-09-18 11:08:31 -0700194 return false;
John Reckdf1742e2017-01-19 15:56:21 -0800195 }
John Reck7075c792017-07-05 14:03:43 -0700196 int index = 0;
John Reck5206a872017-09-18 11:08:31 -0700197 bool hitMergeError = false;
John Reck7075c792017-07-05 14:03:43 -0700198 data->histogramForEach([&](ProfileData::HistogramEntry entry) {
John Reck5206a872017-09-18 11:08:31 -0700199 if (hitMergeError) return;
200
John Reckdf1742e2017-01-19 15:56:21 -0800201 service::GraphicsStatsHistogramBucketProto* bucket;
John Reckdf1742e2017-01-19 15:56:21 -0800202 if (creatingHistogram) {
203 bucket = proto->add_histogram();
John Reck7075c792017-07-05 14:03:43 -0700204 bucket->set_render_millis(entry.renderTimeMs);
John Reckdf1742e2017-01-19 15:56:21 -0800205 } else {
John Reck7075c792017-07-05 14:03:43 -0700206 bucket = proto->mutable_histogram(index);
John Reck5206a872017-09-18 11:08:31 -0700207 if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
208 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(), entry.renderTimeMs);
209 hitMergeError = true;
210 return;
211 }
John Reckdf1742e2017-01-19 15:56:21 -0800212 }
John Reck7075c792017-07-05 14:03:43 -0700213 bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
214 index++;
215 });
John Reck5206a872017-09-18 11:08:31 -0700216 return !hitMergeError;
John Reckdf1742e2017-01-19 15:56:21 -0800217}
218
219static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
220 int32_t pos = percentile * proto->summary().total_frames() / 100;
221 int32_t remaining = proto->summary().total_frames() - pos;
222 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
223 remaining -= it->frame_count();
224 if (remaining <= 0) {
225 return it->render_millis();
226 }
227 }
228 return 0;
229}
230
231void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {
232 // This isn't a full validation, just enough that we can deref at will
John Reck5206a872017-09-18 11:08:31 -0700233 if (proto->package_name().empty() || !proto->has_summary()) {
234 ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
235 proto->package_name().c_str(), proto->has_summary());
236 return;
237 }
John Reckdf1742e2017-01-19 15:56:21 -0800238 dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
239 dprintf(fd, "\nVersion: %d", proto->version_code());
240 dprintf(fd, "\nStats since: %lldns", proto->stats_start());
241 dprintf(fd, "\nStats end: %lldns", proto->stats_end());
242 auto summary = proto->summary();
243 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
244 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
245 (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f);
246 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
247 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
248 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
249 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
250 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
251 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
252 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
253 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
254 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
255 dprintf(fd, "\nHISTOGRAM:");
256 for (const auto& it : proto->histogram()) {
257 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
258 }
259 dprintf(fd, "\n");
260}
261
262void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
263 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
264 service::GraphicsStatsProto statsProto;
265 if (!parseFromFile(path, &statsProto)) {
266 statsProto.Clear();
267 }
John Reck5206a872017-09-18 11:08:31 -0700268 if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
269 return;
270 }
John Reckdf1742e2017-01-19 15:56:21 -0800271 // Although we might not have read any data from the file, merging the existing data
272 // should always fully-initialize the proto
John Reck5206a872017-09-18 11:08:31 -0700273 if (!statsProto.IsInitialized()) {
274 ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str());
275 return;
276 }
277 if (statsProto.package_name().empty() || !statsProto.has_summary()) {
278 ALOGE("missing package_name() '%s' summary %d",
279 statsProto.package_name().c_str(), statsProto.has_summary());
280 return;
281 }
John Reckdf1742e2017-01-19 15:56:21 -0800282 int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
283 if (outFd <= 0) {
284 int err = errno;
285 ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
286 return;
287 }
288 int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
289 if (wrote != sHeaderSize) {
290 int err = errno;
291 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)",
292 path.c_str(), wrote, err, strerror(err));
293 close(outFd);
294 return;
295 }
296 {
John Reck915883b2017-05-03 10:27:20 -0700297 FileOutputStreamLite output(outFd);
John Reckdf1742e2017-01-19 15:56:21 -0800298 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
299 if (output.GetErrno() != 0) {
300 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
301 outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno()));
302 success = false;
303 } else if (!success) {
304 ALOGW("Serialize failed on '%s' unknown error", path.c_str());
305 }
306 }
307 close(outFd);
308}
309
310class GraphicsStatsService::Dump {
311public:
312 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
313 int fd() { return mFd; }
314 DumpType type() { return mType; }
315 service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
316private:
317 int mFd;
318 DumpType mType;
319 service::GraphicsStatsServiceDumpProto mProto;
320};
321
322GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
323 return new Dump(outFd, type);
324}
325
326void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,
327 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
328 service::GraphicsStatsProto statsProto;
329 if (!path.empty() && !parseFromFile(path, &statsProto)) {
330 statsProto.Clear();
331 }
John Reck5206a872017-09-18 11:08:31 -0700332 if (data && !mergeProfileDataIntoProto(
333 &statsProto, package, versionCode, startTime, endTime, data)) {
334 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",
338 path.empty() ? "<empty>" : path.c_str(), data);
339 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) {
350 service::GraphicsStatsProto statsProto;
351 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 */