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     explicit 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; } // NOLINT(google-explicit-constructor)
     60 
     61 private:
     62     int mFd;
     63 };
     64 
     65 class FileOutputStreamLite : public io::ZeroCopyOutputStream {
     66 public:
     67     explicit 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         explicit 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 == 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 
    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 
    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 
    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: %lld", proto->version_code());
    238     dprintf(fd, "\nStats since: %lldns", proto->stats_start());
    239     dprintf(fd, "\nStats end: %lldns", 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 
    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:
    312     Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
    313     int fd() { return mFd; }
    314     DumpType type() { return mType; }
    315     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
    316 
    317 private:
    318     int mFd;
    319     DumpType mType;
    320     protos::GraphicsStatsServiceDumpProto mProto;
    321 };
    322 
    323 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
    324     return new Dump(outFd, type);
    325 }
    326 
    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 
    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 
    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