1 /*
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 #include "protos/graphicsstats.pb.h"
21 
22 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
23 #include <log/log.h>
24 
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <inttypes.h>
28 #include <sys/mman.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 
33 namespace android {
34 namespace uirenderer {
35 
36 using namespace google::protobuf;
37 
38 constexpr int32_t sCurrentFileVersion = 1;
39 constexpr int32_t sHeaderSize = 4;
40 static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
41 
42 constexpr int sHistogramSize = ProfileData::HistogramSize();
43 
44 static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
45                                       const std::string& package, int64_t versionCode,
46                                       int64_t startTime, int64_t endTime, const ProfileData* data);
47 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
48 
49 class FileDescriptor {
50 public:
FileDescriptor(int fd)51     explicit FileDescriptor(int fd) : mFd(fd) {}
~FileDescriptor()52     ~FileDescriptor() {
53         if (mFd != -1) {
54             close(mFd);
55             mFd = -1;
56         }
57     }
valid()58     bool valid() { return mFd != -1; }
operator int()59     operator int() { return mFd; } // NOLINT(google-explicit-constructor)
60 
61 private:
62     int mFd;
63 };
64 
65 class FileOutputStreamLite : public io::ZeroCopyOutputStream {
66 public:
FileOutputStreamLite(int fd)67     explicit FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
~FileOutputStreamLite()68     virtual ~FileOutputStreamLite() {}
69 
GetErrno()70     int GetErrno() { return mCopyAdapter.mErrno; }
71 
Next(void ** data,int * size)72     virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
73 
BackUp(int count)74     virtual void BackUp(int count) override { mImpl.BackUp(count); }
75 
ByteCount() const76     virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
77 
Flush()78     bool Flush() { return mImpl.Flush(); }
79 
80 private:
81     struct FDAdapter : public io::CopyingOutputStream {
82         int mFd;
83         int mErrno = 0;
84 
FDAdapterandroid::uirenderer::FileOutputStreamLite::FDAdapter85         explicit FDAdapter(int fd) : mFd(fd) {}
~FDAdapterandroid::uirenderer::FileOutputStreamLite::FDAdapter86         virtual ~FDAdapter() {}
87 
Writeandroid::uirenderer::FileOutputStreamLite::FDAdapter88         virtual bool Write(const void* buffer, int size) override {
89             int ret;
90             while (size) {
91                 ret = TEMP_FAILURE_RETRY(write(mFd, buffer, size));
92                 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 
parseFromFile(const std::string & path,protos::GraphicsStatsProto * output)106 bool GraphicsStatsService::parseFromFile(const std::string& path,
107                                          protos::GraphicsStatsProto* output) {
108     FileDescriptor fd{open(path.c_str(), O_RDONLY)};
109     if (!fd.valid()) {
110         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     }
118     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,
125                   strerror(err), (int)sb.st_size);
126         }
127         return false;
128     }
129     void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
130     if (addr == MAP_FAILED) {
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);
142         munmap(addr, sb.st_size);
143         return false;
144     }
145 
146     void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
147     int dataSize = sb.st_size - sHeaderSize;
148     io::ArrayInputStream input{data, dataSize};
149     bool success = output->ParseFromZeroCopyStream(&input);
150     if (!success) {
151         ALOGW("Parse failed on '%s' error='%s'", path.c_str(),
152               output->InitializationErrorString().c_str());
153     }
154     munmap(addr, sb.st_size);
155     return success;
156 }
157 
mergeProfileDataIntoProto(protos::GraphicsStatsProto * proto,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)158 bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
159                                int64_t versionCode, int64_t startTime, int64_t endTime,
160                                const ProfileData* data) {
161     if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
162         proto->set_stats_start(startTime);
163     }
164     if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
165         proto->set_stats_end(endTime);
166     }
167     proto->set_package_name(package);
168     proto->set_version_code(versionCode);
169     auto summary = proto->mutable_summary();
170     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
171     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
172     summary->set_missed_vsync_count(summary->missed_vsync_count() +
173                                     data->jankTypeCount(kMissedVsync));
174     summary->set_high_input_latency_count(summary->high_input_latency_count() +
175                                           data->jankTypeCount(kHighInputLatency));
176     summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
177                                       data->jankTypeCount(kSlowUI));
178     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
179                                           data->jankTypeCount(kSlowSync));
180     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
181     summary->set_missed_deadline_count(summary->missed_deadline_count()
182             + data->jankTypeCount(kMissedDeadline));
183 
184     bool creatingHistogram = false;
185     if (proto->histogram_size() == 0) {
186         proto->mutable_histogram()->Reserve(sHistogramSize);
187         creatingHistogram = true;
188     } else if (proto->histogram_size() != sHistogramSize) {
189         ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(),
190               sHistogramSize);
191         return false;
192     }
193     int index = 0;
194     bool hitMergeError = false;
195     data->histogramForEach([&](ProfileData::HistogramEntry entry) {
196         if (hitMergeError) return;
197 
198         protos::GraphicsStatsHistogramBucketProto* bucket;
199         if (creatingHistogram) {
200             bucket = proto->add_histogram();
201             bucket->set_render_millis(entry.renderTimeMs);
202         } else {
203             bucket = proto->mutable_histogram(index);
204             if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
205                 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(),
206                       entry.renderTimeMs);
207                 hitMergeError = true;
208                 return;
209             }
210         }
211         bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
212         index++;
213     });
214     return !hitMergeError;
215 }
216 
findPercentile(protos::GraphicsStatsProto * proto,int percentile)217 static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) {
218     int32_t pos = percentile * proto->summary().total_frames() / 100;
219     int32_t remaining = proto->summary().total_frames() - pos;
220     for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
221         remaining -= it->frame_count();
222         if (remaining <= 0) {
223             return it->render_millis();
224         }
225     }
226     return 0;
227 }
228 
dumpAsTextToFd(protos::GraphicsStatsProto * proto,int fd)229 void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) {
230     // This isn't a full validation, just enough that we can deref at will
231     if (proto->package_name().empty() || !proto->has_summary()) {
232         ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
233               proto->package_name().c_str(), proto->has_summary());
234         return;
235     }
236     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
237     dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
238     dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
239     dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end());
240     auto summary = proto->summary();
241     dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
242     dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
243             (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f);
244     dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
245     dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
246     dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
247     dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
248     dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
249     dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
250     dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
251     dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
252     dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
253     dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_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 
saveBuffer(const std::string & path,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)261 void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
262                                       int64_t versionCode, int64_t startTime, int64_t endTime,
263                                       const ProfileData* data) {
264     protos::GraphicsStatsProto statsProto;
265     if (!parseFromFile(path, &statsProto)) {
266         statsProto.Clear();
267     }
268     if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
269         return;
270     }
271     // Although we might not have read any data from the file, merging the existing data
272     // should always fully-initialize the proto
273     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", statsProto.package_name().c_str(),
279               statsProto.has_summary());
280         return;
281     }
282     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)", path.c_str(), wrote, err,
292               strerror(err));
293         close(outFd);
294         return;
295     }
296     {
297         FileOutputStreamLite output(outFd);
298         bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
299         if (output.GetErrno() != 0) {
300             ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(),
301                   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 
310 class GraphicsStatsService::Dump {
311 public:
Dump(int outFd,DumpType type)312     Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
fd()313     int fd() { return mFd; }
type()314     DumpType type() { return mType; }
proto()315     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
316 
317 private:
318     int mFd;
319     DumpType mType;
320     protos::GraphicsStatsServiceDumpProto mProto;
321 };
322 
createDump(int outFd,DumpType type)323 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
324     return new Dump(outFd, type);
325 }
326 
addToDump(Dump * dump,const std::string & path,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)327 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
328                                      const std::string& package, int64_t versionCode,
329                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
330     protos::GraphicsStatsProto statsProto;
331     if (!path.empty() && !parseFromFile(path, &statsProto)) {
332         statsProto.Clear();
333     }
334     if (data &&
335         !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
336         return;
337     }
338     if (!statsProto.IsInitialized()) {
339         ALOGW("Failed to load profile data from path '%s' and data %p",
340               path.empty() ? "<empty>" : path.c_str(), data);
341         return;
342     }
343 
344     if (dump->type() == DumpType::Protobuf) {
345         dump->proto().add_stats()->CopyFrom(statsProto);
346     } else {
347         dumpAsTextToFd(&statsProto, dump->fd());
348     }
349 }
350 
addToDump(Dump * dump,const std::string & path)351 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
352     protos::GraphicsStatsProto statsProto;
353     if (!parseFromFile(path, &statsProto)) {
354         return;
355     }
356     if (dump->type() == DumpType::Protobuf) {
357         dump->proto().add_stats()->CopyFrom(statsProto);
358     } else {
359         dumpAsTextToFd(&statsProto, dump->fd());
360     }
361 }
362 
finishDump(Dump * dump)363 void GraphicsStatsService::finishDump(Dump* dump) {
364     if (dump->type() == DumpType::Protobuf) {
365         FileOutputStreamLite stream(dump->fd());
366         dump->proto().SerializeToZeroCopyStream(&stream);
367     }
368     delete dump;
369 }
370 
371 } /* namespace uirenderer */
372 } /* namespace android */
373