Home | History | Annotate | Download | only in service
      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:
     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; }
     60 
     61 private:
     62     int mFd;
     63 };
     64 
     65 class FileOutputStreamLite : public io::ZeroCopyOutputStream {
     66 public:
     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 { return mImpl.Next(data, size); }
     73 
     74     virtual void BackUp(int count) override { mImpl.BackUp(count); }
     75 
     76     virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
     77 
     78     bool Flush() { return mImpl.Flush(); }
     79 
     80 private:
     81     struct FDAdapter : public io::CopyingOutputStream {
     82         int mFd;
     83         int mErrno = 0;
     84 
     85         FDAdapter(int fd) : mFd(fd) {}
     86         virtual ~FDAdapter() {}
     87 
     88         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 
    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 
    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 
    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 
    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 
    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:
    310     Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
    311     int fd() { return mFd; }
    312     DumpType type() { return mType; }
    313     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
    314 
    315 private:
    316     int mFd;
    317     DumpType mType;
    318     protos::GraphicsStatsServiceDumpProto mProto;
    319 };
    320 
    321 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
    322     return new Dump(outFd, type);
    323 }
    324 
    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 
    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 
    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