blob: afb1193657e1146ccd6222e37fb1f8e54e67852c [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
25#include <inttypes.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <unistd.h>
John Reck915883b2017-05-03 10:27:20 -070030#include <sys/mman.h>
John Reckdf1742e2017-01-19 15:56:21 -080031
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
John Reck7075c792017-07-05 14:03:43 -070041constexpr int sHistogramSize = ProfileData::HistogramSize();
John Reckdf1742e2017-01-19 15:56:21 -080042
John Reck5206a872017-09-18 11:08:31 -070043static bool mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
John Reckdf1742e2017-01-19 15:56:21 -080044 const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
45 const ProfileData* data);
46static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
47
John Reck915883b2017-05-03 10:27:20 -070048class FileDescriptor {
49public:
50 FileDescriptor(int fd) : mFd(fd) {}
51 ~FileDescriptor() {
52 if (mFd != -1) {
53 close(mFd);
54 mFd = -1;
55 }
56 }
57 bool valid() { return mFd != -1; }
58 operator int() { return mFd; }
59private:
60 int mFd;
61};
62
63class FileOutputStreamLite : public io::ZeroCopyOutputStream {
64public:
65 FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
66 virtual ~FileOutputStreamLite() {}
67
68 int GetErrno() { return mCopyAdapter.mErrno; }
69
70 virtual bool Next(void** data, int* size) override {
71 return mImpl.Next(data, size);
72 }
73
74 virtual void BackUp(int count) override {
75 mImpl.BackUp(count);
76 }
77
78 virtual int64 ByteCount() const override {
79 return mImpl.ByteCount();
80 }
81
82 bool Flush() {
83 return mImpl.Flush();
84 }
85
86private:
87 struct FDAdapter : public io::CopyingOutputStream {
88 int mFd;
89 int mErrno = 0;
90
91 FDAdapter(int fd) : mFd(fd) {}
92 virtual ~FDAdapter() {}
93
94 virtual bool Write(const void* buffer, int size) override {
95 int ret;
96 while (size) {
97 ret = TEMP_FAILURE_RETRY( write(mFd, buffer, size) );
98 if (ret <= 0) {
99 mErrno = errno;
100 return false;
101 }
102 size -= ret;
103 }
104 return true;
105 }
106 };
107
108 FileOutputStreamLite::FDAdapter mCopyAdapter;
109 io::CopyingOutputStreamAdaptor mImpl;
110};
111
John Reckdf1742e2017-01-19 15:56:21 -0800112bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
113
John Reck915883b2017-05-03 10:27:20 -0700114 FileDescriptor fd{open(path.c_str(), O_RDONLY)};
115 if (!fd.valid()) {
John Reckdf1742e2017-01-19 15:56:21 -0800116 int err = errno;
117 // The file not existing is normal for addToDump(), so only log if
118 // we get an unexpected error
119 if (err != ENOENT) {
120 ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
121 }
122 return false;
123 }
John Reck915883b2017-05-03 10:27:20 -0700124 struct stat sb;
125 if (fstat(fd, &sb) || sb.st_size < sHeaderSize) {
126 int err = errno;
127 // The file not existing is normal for addToDump(), so only log if
128 // we get an unexpected error
129 if (err != ENOENT) {
130 ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err,
131 strerror(err), (int) sb.st_size);
132 }
133 return false;
134 }
135 void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
136 if (!addr) {
137 int err = errno;
138 // The file not existing is normal for addToDump(), so only log if
139 // we get an unexpected error
140 if (err != ENOENT) {
141 ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
142 }
143 return false;
144 }
145 uint32_t file_version = *reinterpret_cast<uint32_t*>(addr);
146 if (file_version != sCurrentFileVersion) {
147 ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version);
John Reckdf1742e2017-01-19 15:56:21 -0800148 return false;
149 }
150
John Reck915883b2017-05-03 10:27:20 -0700151 void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
152 int dataSize = sb.st_size - sHeaderSize;
153 io::ArrayInputStream input{data, dataSize};
John Reckdf1742e2017-01-19 15:56:21 -0800154 bool success = output->ParseFromZeroCopyStream(&input);
John Reck915883b2017-05-03 10:27:20 -0700155 if (!success) {
John Reckdf1742e2017-01-19 15:56:21 -0800156 ALOGW("Parse failed on '%s' error='%s'",
157 path.c_str(), output->InitializationErrorString().c_str());
158 }
John Reckdf1742e2017-01-19 15:56:21 -0800159 return success;
160}
161
John Reck5206a872017-09-18 11:08:31 -0700162bool mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package,
John Reckdf1742e2017-01-19 15:56:21 -0800163 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
164 if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
165 proto->set_stats_start(startTime);
166 }
167 if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
168 proto->set_stats_end(endTime);
169 }
170 proto->set_package_name(package);
171 proto->set_version_code(versionCode);
172 auto summary = proto->mutable_summary();
John Reck7075c792017-07-05 14:03:43 -0700173 summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
174 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
John Reckdf1742e2017-01-19 15:56:21 -0800175 summary->set_missed_vsync_count(
John Reck7075c792017-07-05 14:03:43 -0700176 summary->missed_vsync_count() + data->jankTypeCount(kMissedVsync));
John Reckdf1742e2017-01-19 15:56:21 -0800177 summary->set_high_input_latency_count(
John Reck7075c792017-07-05 14:03:43 -0700178 summary->high_input_latency_count() + data->jankTypeCount(kHighInputLatency));
John Reckdf1742e2017-01-19 15:56:21 -0800179 summary->set_slow_ui_thread_count(
John Reck7075c792017-07-05 14:03:43 -0700180 summary->slow_ui_thread_count() + data->jankTypeCount(kSlowUI));
John Reckdf1742e2017-01-19 15:56:21 -0800181 summary->set_slow_bitmap_upload_count(
John Reck7075c792017-07-05 14:03:43 -0700182 summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync));
John Reckdf1742e2017-01-19 15:56:21 -0800183 summary->set_slow_draw_count(
John Reck7075c792017-07-05 14:03:43 -0700184 summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
John Reckdf1742e2017-01-19 15:56:21 -0800185
186 bool creatingHistogram = false;
187 if (proto->histogram_size() == 0) {
188 proto->mutable_histogram()->Reserve(sHistogramSize);
189 creatingHistogram = true;
190 } else if (proto->histogram_size() != sHistogramSize) {
John Reck5206a872017-09-18 11:08:31 -0700191 ALOGE("Histogram size mismatch, proto is %d expected %d",
John Reckdf1742e2017-01-19 15:56:21 -0800192 proto->histogram_size(), sHistogramSize);
John Reck5206a872017-09-18 11:08:31 -0700193 return false;
John Reckdf1742e2017-01-19 15:56:21 -0800194 }
John Reck7075c792017-07-05 14:03:43 -0700195 int index = 0;
John Reck5206a872017-09-18 11:08:31 -0700196 bool hitMergeError = false;
John Reck7075c792017-07-05 14:03:43 -0700197 data->histogramForEach([&](ProfileData::HistogramEntry entry) {
John Reck5206a872017-09-18 11:08:31 -0700198 if (hitMergeError) return;
199
John Reckdf1742e2017-01-19 15:56:21 -0800200 service::GraphicsStatsHistogramBucketProto* bucket;
John Reckdf1742e2017-01-19 15:56:21 -0800201 if (creatingHistogram) {
202 bucket = proto->add_histogram();
John Reck7075c792017-07-05 14:03:43 -0700203 bucket->set_render_millis(entry.renderTimeMs);
John Reckdf1742e2017-01-19 15:56:21 -0800204 } else {
John Reck7075c792017-07-05 14:03:43 -0700205 bucket = proto->mutable_histogram(index);
John Reck5206a872017-09-18 11:08:31 -0700206 if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
207 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(), entry.renderTimeMs);
208 hitMergeError = true;
209 return;
210 }
John Reckdf1742e2017-01-19 15:56:21 -0800211 }
John Reck7075c792017-07-05 14:03:43 -0700212 bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
213 index++;
214 });
John Reck5206a872017-09-18 11:08:31 -0700215 return !hitMergeError;
John Reckdf1742e2017-01-19 15:56:21 -0800216}
217
218static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
219 int32_t pos = percentile * proto->summary().total_frames() / 100;
220 int32_t remaining = proto->summary().total_frames() - pos;
221 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
222 remaining -= it->frame_count();
223 if (remaining <= 0) {
224 return it->render_millis();
225 }
226 }
227 return 0;
228}
229
230void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {
231 // This isn't a full validation, just enough that we can deref at will
John Reck5206a872017-09-18 11:08:31 -0700232 if (proto->package_name().empty() || !proto->has_summary()) {
233 ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
234 proto->package_name().c_str(), proto->has_summary());
235 return;
236 }
John Reckdf1742e2017-01-19 15:56:21 -0800237 dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
238 dprintf(fd, "\nVersion: %d", proto->version_code());
239 dprintf(fd, "\nStats since: %lldns", proto->stats_start());
240 dprintf(fd, "\nStats end: %lldns", proto->stats_end());
241 auto summary = proto->summary();
242 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
243 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
244 (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f);
245 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
246 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
247 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
248 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
249 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
250 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
251 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
252 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
253 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
254 dprintf(fd, "\nHISTOGRAM:");
255 for (const auto& it : proto->histogram()) {
256 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
257 }
258 dprintf(fd, "\n");
259}
260
261void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
262 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
263 service::GraphicsStatsProto statsProto;
264 if (!parseFromFile(path, &statsProto)) {
265 statsProto.Clear();
266 }
John Reck5206a872017-09-18 11:08:31 -0700267 if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
268 return;
269 }
John Reckdf1742e2017-01-19 15:56:21 -0800270 // Although we might not have read any data from the file, merging the existing data
271 // should always fully-initialize the proto
John Reck5206a872017-09-18 11:08:31 -0700272 if (!statsProto.IsInitialized()) {
273 ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str());
274 return;
275 }
276 if (statsProto.package_name().empty() || !statsProto.has_summary()) {
277 ALOGE("missing package_name() '%s' summary %d",
278 statsProto.package_name().c_str(), statsProto.has_summary());
279 return;
280 }
John Reckdf1742e2017-01-19 15:56:21 -0800281 int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
282 if (outFd <= 0) {
283 int err = errno;
284 ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
285 return;
286 }
287 int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
288 if (wrote != sHeaderSize) {
289 int err = errno;
290 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)",
291 path.c_str(), wrote, err, strerror(err));
292 close(outFd);
293 return;
294 }
295 {
John Reck915883b2017-05-03 10:27:20 -0700296 FileOutputStreamLite output(outFd);
John Reckdf1742e2017-01-19 15:56:21 -0800297 bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
298 if (output.GetErrno() != 0) {
299 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
300 outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno()));
301 success = false;
302 } else if (!success) {
303 ALOGW("Serialize failed on '%s' unknown error", path.c_str());
304 }
305 }
306 close(outFd);
307}
308
309class GraphicsStatsService::Dump {
310public:
311 Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
312 int fd() { return mFd; }
313 DumpType type() { return mType; }
314 service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
315private:
316 int mFd;
317 DumpType mType;
318 service::GraphicsStatsServiceDumpProto mProto;
319};
320
321GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
322 return new Dump(outFd, type);
323}
324
325void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,
326 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
327 service::GraphicsStatsProto statsProto;
328 if (!path.empty() && !parseFromFile(path, &statsProto)) {
329 statsProto.Clear();
330 }
John Reck5206a872017-09-18 11:08:31 -0700331 if (data && !mergeProfileDataIntoProto(
332 &statsProto, package, versionCode, startTime, endTime, data)) {
333 return;
John Reckdf1742e2017-01-19 15:56:21 -0800334 }
335 if (!statsProto.IsInitialized()) {
336 ALOGW("Failed to load profile data from path '%s' and data %p",
337 path.empty() ? "<empty>" : path.c_str(), data);
338 return;
339 }
340
341 if (dump->type() == DumpType::Protobuf) {
342 dump->proto().add_stats()->CopyFrom(statsProto);
343 } else {
344 dumpAsTextToFd(&statsProto, dump->fd());
345 }
346}
347
348void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
349 service::GraphicsStatsProto statsProto;
350 if (!parseFromFile(path, &statsProto)) {
351 return;
352 }
353 if (dump->type() == DumpType::Protobuf) {
354 dump->proto().add_stats()->CopyFrom(statsProto);
355 } else {
356 dumpAsTextToFd(&statsProto, dump->fd());
357 }
358}
359
360void GraphicsStatsService::finishDump(Dump* dump) {
361 if (dump->type() == DumpType::Protobuf) {
John Reck915883b2017-05-03 10:27:20 -0700362 FileOutputStreamLite stream(dump->fd());
John Reckdf1742e2017-01-19 15:56:21 -0800363 dump->proto().SerializeToZeroCopyStream(&stream);
364 }
365 delete dump;
366}
367
368} /* namespace uirenderer */
369} /* namespace android */