blob: 87eaa6add45971bc9550197949c99d5396b6fe90 [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
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
John Reck915883b2017-05-03 10:27:20 -070050class FileDescriptor {
51public:
52 FileDescriptor(int fd) : mFd(fd) {}
53 ~FileDescriptor() {
54 if (mFd != -1) {
55 close(mFd);
56 mFd = -1;
57 }
58 }
59 bool valid() { return mFd != -1; }
60 operator int() { return mFd; }
61private:
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
72 virtual bool Next(void** data, int* size) override {
73 return mImpl.Next(data, size);
74 }
75
76 virtual void BackUp(int count) override {
77 mImpl.BackUp(count);
78 }
79
80 virtual int64 ByteCount() const override {
81 return mImpl.ByteCount();
82 }
83
84 bool Flush() {
85 return mImpl.Flush();
86 }
87
88private:
89 struct FDAdapter : public io::CopyingOutputStream {
90 int mFd;
91 int mErrno = 0;
92
93 FDAdapter(int fd) : mFd(fd) {}
94 virtual ~FDAdapter() {}
95
96 virtual bool Write(const void* buffer, int size) override {
97 int ret;
98 while (size) {
99 ret = TEMP_FAILURE_RETRY( write(mFd, buffer, size) );
100 if (ret <= 0) {
101 mErrno = errno;
102 return false;
103 }
104 size -= ret;
105 }
106 return true;
107 }
108 };
109
110 FileOutputStreamLite::FDAdapter mCopyAdapter;
111 io::CopyingOutputStreamAdaptor mImpl;
112};
113
John Reckdf1742e2017-01-19 15:56:21 -0800114bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
115
John Reck915883b2017-05-03 10:27:20 -0700116 FileDescriptor fd{open(path.c_str(), O_RDONLY)};
117 if (!fd.valid()) {
John Reckdf1742e2017-01-19 15:56:21 -0800118 int err = errno;
119 // The file not existing is normal for addToDump(), so only log if
120 // we get an unexpected error
121 if (err != ENOENT) {
122 ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
123 }
124 return false;
125 }
John Reck915883b2017-05-03 10:27:20 -0700126 struct stat sb;
127 if (fstat(fd, &sb) || sb.st_size < sHeaderSize) {
128 int err = errno;
129 // The file not existing is normal for addToDump(), so only log if
130 // we get an unexpected error
131 if (err != ENOENT) {
132 ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err,
133 strerror(err), (int) sb.st_size);
134 }
135 return false;
136 }
137 void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
138 if (!addr) {
139 int err = errno;
140 // The file not existing is normal for addToDump(), so only log if
141 // we get an unexpected error
142 if (err != ENOENT) {
143 ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
144 }
145 return false;
146 }
147 uint32_t file_version = *reinterpret_cast<uint32_t*>(addr);
148 if (file_version != sCurrentFileVersion) {
149 ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version);
John Reckdf1742e2017-01-19 15:56:21 -0800150 return false;
151 }
152
John Reck915883b2017-05-03 10:27:20 -0700153 void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
154 int dataSize = sb.st_size - sHeaderSize;
155 io::ArrayInputStream input{data, dataSize};
John Reckdf1742e2017-01-19 15:56:21 -0800156 bool success = output->ParseFromZeroCopyStream(&input);
John Reck915883b2017-05-03 10:27:20 -0700157 if (!success) {
John Reckdf1742e2017-01-19 15:56:21 -0800158 ALOGW("Parse failed on '%s' error='%s'",
159 path.c_str(), output->InitializationErrorString().c_str());
160 }
John Reckdf1742e2017-01-19 15:56:21 -0800161 return success;
162}
163
164void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package,
165 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
166 if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
167 proto->set_stats_start(startTime);
168 }
169 if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
170 proto->set_stats_end(endTime);
171 }
172 proto->set_package_name(package);
173 proto->set_version_code(versionCode);
174 auto summary = proto->mutable_summary();
175 summary->set_total_frames(summary->total_frames() + data->totalFrameCount);
176 summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount);
177 summary->set_missed_vsync_count(
178 summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]);
179 summary->set_high_input_latency_count(
180 summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]);
181 summary->set_slow_ui_thread_count(
182 summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]);
183 summary->set_slow_bitmap_upload_count(
184 summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]);
185 summary->set_slow_draw_count(
186 summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]);
187
188 bool creatingHistogram = false;
189 if (proto->histogram_size() == 0) {
190 proto->mutable_histogram()->Reserve(sHistogramSize);
191 creatingHistogram = true;
192 } else if (proto->histogram_size() != sHistogramSize) {
193 LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d",
194 proto->histogram_size(), sHistogramSize);
195 }
196 for (size_t i = 0; i < data->frameCounts.size(); i++) {
197 service::GraphicsStatsHistogramBucketProto* bucket;
198 int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i);
199 if (creatingHistogram) {
200 bucket = proto->add_histogram();
201 bucket->set_render_millis(renderTime);
202 } else {
203 bucket = proto->mutable_histogram(i);
204 LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
205 "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
206 }
207 bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]);
208 }
209 for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
210 service::GraphicsStatsHistogramBucketProto* bucket;
211 int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i);
212 if (creatingHistogram) {
213 bucket = proto->add_histogram();
214 bucket->set_render_millis(renderTime);
215 } else {
216 constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value;
217 bucket = proto->mutable_histogram(offset + i);
218 LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
219 "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
220 }
221 bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]);
222 }
223}
224
225static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
226 int32_t pos = percentile * proto->summary().total_frames() / 100;
227 int32_t remaining = proto->summary().total_frames() - pos;
228 for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
229 remaining -= it->frame_count();
230 if (remaining <= 0) {
231 return it->render_millis();
232 }
233 }
234 return 0;
235}
236
237void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {
238 // This isn't a full validation, just enough that we can deref at will
239 LOG_ALWAYS_FATAL_IF(proto->package_name().empty()
240 || !proto->has_summary(), "package_name() '%s' summary %d",
241 proto->package_name().c_str(), proto->has_summary());
242 dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
243 dprintf(fd, "\nVersion: %d", proto->version_code());
244 dprintf(fd, "\nStats since: %lldns", proto->stats_start());
245 dprintf(fd, "\nStats end: %lldns", proto->stats_end());
246 auto summary = proto->summary();
247 dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
248 dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
249 (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f);
250 dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
251 dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
252 dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
253 dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
254 dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
255 dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
256 dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
257 dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
258 dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
259 dprintf(fd, "\nHISTOGRAM:");
260 for (const auto& it : proto->histogram()) {
261 dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
262 }
263 dprintf(fd, "\n");
264}
265
266void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
267 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
268 service::GraphicsStatsProto statsProto;
269 if (!parseFromFile(path, &statsProto)) {
270 statsProto.Clear();
271 }
272 mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
273 // Although we might not have read any data from the file, merging the existing data
274 // should always fully-initialize the proto
275 LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s",
276 statsProto.InitializationErrorString().c_str());
277 LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty()
278 || !statsProto.has_summary(), "package_name() '%s' summary %d",
279 statsProto.package_name().c_str(), statsProto.has_summary());
280 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;
289 ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)",
290 path.c_str(), wrote, err, strerror(err));
291 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) {
298 ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
299 outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno()));
300 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; }
313 service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
314private:
315 int mFd;
316 DumpType mType;
317 service::GraphicsStatsServiceDumpProto mProto;
318};
319
320GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
321 return new Dump(outFd, type);
322}
323
324void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,
325 int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
326 service::GraphicsStatsProto statsProto;
327 if (!path.empty() && !parseFromFile(path, &statsProto)) {
328 statsProto.Clear();
329 }
330 if (data) {
331 mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
332 }
333 if (!statsProto.IsInitialized()) {
334 ALOGW("Failed to load profile data from path '%s' and data %p",
335 path.empty() ? "<empty>" : path.c_str(), data);
336 return;
337 }
338
339 if (dump->type() == DumpType::Protobuf) {
340 dump->proto().add_stats()->CopyFrom(statsProto);
341 } else {
342 dumpAsTextToFd(&statsProto, dump->fd());
343 }
344}
345
346void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
347 service::GraphicsStatsProto statsProto;
348 if (!parseFromFile(path, &statsProto)) {
349 return;
350 }
351 if (dump->type() == DumpType::Protobuf) {
352 dump->proto().add_stats()->CopyFrom(statsProto);
353 } else {
354 dumpAsTextToFd(&statsProto, dump->fd());
355 }
356}
357
358void GraphicsStatsService::finishDump(Dump* dump) {
359 if (dump->type() == DumpType::Protobuf) {
John Reck915883b2017-05-03 10:27:20 -0700360 FileOutputStreamLite stream(dump->fd());
John Reckdf1742e2017-01-19 15:56:21 -0800361 dump->proto().SerializeToZeroCopyStream(&stream);
362 }
363 delete dump;
364}
365
366} /* namespace uirenderer */
367} /* namespace android */