• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     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; }
60 
61 private:
62     int mFd;
63 };
64 
65 class FileOutputStreamLite : public io::ZeroCopyOutputStream {
66 public:
FileOutputStreamLite(int fd)67     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         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) {
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         return false;
143     }
144 
145     void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
146     int dataSize = sb.st_size - sHeaderSize;
147     io::ArrayInputStream input{data, dataSize};
148     bool success = output->ParseFromZeroCopyStream(&input);
149     if (!success) {
150         ALOGW("Parse failed on '%s' error='%s'", path.c_str(),
151               output->InitializationErrorString().c_str());
152     }
153     return success;
154 }
155 
mergeProfileDataIntoProto(protos::GraphicsStatsProto * proto,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)156 bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
157                                int64_t versionCode, int64_t startTime, int64_t endTime,
158                                const ProfileData* data) {
159     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();
168     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
169     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
170     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));
179     summary->set_missed_deadline_count(summary->missed_deadline_count()
180             + data->jankTypeCount(kMissedDeadline));
181 
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) {
187         ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(),
188               sHistogramSize);
189         return false;
190     }
191     int index = 0;
192     bool hitMergeError = false;
193     data->histogramForEach([&](ProfileData::HistogramEntry entry) {
194         if (hitMergeError) return;
195 
196         protos::GraphicsStatsHistogramBucketProto* bucket;
197         if (creatingHistogram) {
198             bucket = proto->add_histogram();
199             bucket->set_render_millis(entry.renderTimeMs);
200         } else {
201             bucket = proto->mutable_histogram(index);
202             if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) {
203                 ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(),
204                       entry.renderTimeMs);
205                 hitMergeError = true;
206                 return;
207             }
208         }
209         bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
210         index++;
211     });
212     return !hitMergeError;
213 }
214 
findPercentile(protos::GraphicsStatsProto * proto,int percentile)215 static int32_t findPercentile(protos::GraphicsStatsProto* proto, int percentile) {
216     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 
dumpAsTextToFd(protos::GraphicsStatsProto * proto,int fd)227 void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) {
228     // This isn't a full validation, just enough that we can deref at will
229     if (proto->package_name().empty() || !proto->has_summary()) {
230         ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
231               proto->package_name().c_str(), proto->has_summary());
232         return;
233     }
234     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
235     dprintf(fd, "\nVersion: %lld", proto->version_code());
236     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(),
241             (float)summary.janky_frames() / (float)summary.total_frames() * 100.0f);
242     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());
251     dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count());
252     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 
saveBuffer(const std::string & path,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)259 void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
260                                       int64_t versionCode, int64_t startTime, int64_t endTime,
261                                       const ProfileData* data) {
262     protos::GraphicsStatsProto statsProto;
263     if (!parseFromFile(path, &statsProto)) {
264         statsProto.Clear();
265     }
266     if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
267         return;
268     }
269     // Although we might not have read any data from the file, merging the existing data
270     // should always fully-initialize the proto
271     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()) {
276         ALOGE("missing package_name() '%s' summary %d", statsProto.package_name().c_str(),
277               statsProto.has_summary());
278         return;
279     }
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)", path.c_str(), wrote, err,
290               strerror(err));
291         close(outFd);
292         return;
293     }
294     {
295         FileOutputStreamLite output(outFd);
296         bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
297         if (output.GetErrno() != 0) {
298             ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", outFd, path.c_str(),
299                   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 
308 class GraphicsStatsService::Dump {
309 public:
Dump(int outFd,DumpType type)310     Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
fd()311     int fd() { return mFd; }
type()312     DumpType type() { return mType; }
proto()313     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
314 
315 private:
316     int mFd;
317     DumpType mType;
318     protos::GraphicsStatsServiceDumpProto mProto;
319 };
320 
createDump(int outFd,DumpType type)321 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
322     return new Dump(outFd, type);
323 }
324 
addToDump(Dump * dump,const std::string & path,const std::string & package,int64_t versionCode,int64_t startTime,int64_t endTime,const ProfileData * data)325 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
326                                      const std::string& package, int64_t versionCode,
327                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
328     protos::GraphicsStatsProto statsProto;
329     if (!path.empty() && !parseFromFile(path, &statsProto)) {
330         statsProto.Clear();
331     }
332     if (data &&
333         !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
334         return;
335     }
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 
addToDump(Dump * dump,const std::string & path)349 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
350     protos::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 
finishDump(Dump * dump)361 void GraphicsStatsService::finishDump(Dump* dump) {
362     if (dump->type() == DumpType::Protobuf) {
363         FileOutputStreamLite stream(dump->fd());
364         dump->proto().SerializeToZeroCopyStream(&stream);
365     }
366     delete dump;
367 }
368 
369 } /* namespace uirenderer */
370 } /* namespace android */
371