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