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.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 
     31 namespace android {
     32 namespace uirenderer {
     33 
     34 using namespace google::protobuf;
     35 
     36 constexpr int32_t sCurrentFileVersion = 1;
     37 constexpr int32_t sHeaderSize = 4;
     38 static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
     39 
     40 constexpr int sHistogramSize =
     41         std::tuple_size<decltype(ProfileData::frameCounts)>::value +
     42         std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value;
     43 
     44 static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
     45         const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
     46         const ProfileData* data);
     47 static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
     48 
     49 bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
     50 
     51     int fd = open(path.c_str(), O_RDONLY);
     52     if (fd == -1) {
     53         int err = errno;
     54         // The file not existing is normal for addToDump(), so only log if
     55         // we get an unexpected error
     56         if (err != ENOENT) {
     57             ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
     58         }
     59         return false;
     60     }
     61     uint32_t file_version;
     62     ssize_t bytesRead = read(fd, &file_version, sHeaderSize);
     63     if (bytesRead != sHeaderSize || file_version != sCurrentFileVersion) {
     64         ALOGW("Failed to read '%s', bytesRead=%zd file_version=%d", path.c_str(), bytesRead,
     65                 file_version);
     66         close(fd);
     67         return false;
     68     }
     69 
     70     io::FileInputStream input(fd);
     71     bool success = output->ParseFromZeroCopyStream(&input);
     72     if (input.GetErrno() != 0) {
     73         ALOGW("Error reading from fd=%d, path='%s' err=%d (%s)",
     74                 fd, path.c_str(), input.GetErrno(), strerror(input.GetErrno()));
     75         success = false;
     76     } else if (!success) {
     77         ALOGW("Parse failed on '%s' error='%s'",
     78                 path.c_str(), output->InitializationErrorString().c_str());
     79     }
     80     close(fd);
     81     return success;
     82 }
     83 
     84 void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package,
     85         int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
     86     if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
     87         proto->set_stats_start(startTime);
     88     }
     89     if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
     90         proto->set_stats_end(endTime);
     91     }
     92     proto->set_package_name(package);
     93     proto->set_version_code(versionCode);
     94     auto summary = proto->mutable_summary();
     95     summary->set_total_frames(summary->total_frames() + data->totalFrameCount);
     96     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount);
     97     summary->set_missed_vsync_count(
     98             summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]);
     99     summary->set_high_input_latency_count(
    100             summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]);
    101     summary->set_slow_ui_thread_count(
    102             summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]);
    103     summary->set_slow_bitmap_upload_count(
    104             summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]);
    105     summary->set_slow_draw_count(
    106             summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]);
    107 
    108     bool creatingHistogram = false;
    109     if (proto->histogram_size() == 0) {
    110         proto->mutable_histogram()->Reserve(sHistogramSize);
    111         creatingHistogram = true;
    112     } else if (proto->histogram_size() != sHistogramSize) {
    113         LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d",
    114                 proto->histogram_size(), sHistogramSize);
    115     }
    116     for (size_t i = 0; i < data->frameCounts.size(); i++) {
    117         service::GraphicsStatsHistogramBucketProto* bucket;
    118         int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i);
    119         if (creatingHistogram) {
    120             bucket = proto->add_histogram();
    121             bucket->set_render_millis(renderTime);
    122         } else {
    123             bucket = proto->mutable_histogram(i);
    124             LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
    125                     "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
    126         }
    127         bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]);
    128     }
    129     for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
    130         service::GraphicsStatsHistogramBucketProto* bucket;
    131         int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i);
    132         if (creatingHistogram) {
    133             bucket = proto->add_histogram();
    134             bucket->set_render_millis(renderTime);
    135         } else {
    136             constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value;
    137             bucket = proto->mutable_histogram(offset + i);
    138             LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
    139                     "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
    140         }
    141         bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]);
    142     }
    143 }
    144 
    145 static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
    146     int32_t pos = percentile * proto->summary().total_frames() / 100;
    147     int32_t remaining = proto->summary().total_frames() - pos;
    148     for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
    149         remaining -= it->frame_count();
    150         if (remaining <= 0) {
    151             return it->render_millis();
    152         }
    153     }
    154     return 0;
    155 }
    156 
    157 void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {
    158     // This isn't a full validation, just enough that we can deref at will
    159     LOG_ALWAYS_FATAL_IF(proto->package_name().empty()
    160             || !proto->has_summary(), "package_name() '%s' summary %d",
    161             proto->package_name().c_str(), proto->has_summary());
    162     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
    163     dprintf(fd, "\nVersion: %d", proto->version_code());
    164     dprintf(fd, "\nStats since: %lldns", proto->stats_start());
    165     dprintf(fd, "\nStats end: %lldns", proto->stats_end());
    166     auto summary = proto->summary();
    167     dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
    168     dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
    169             (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f);
    170     dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
    171     dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
    172     dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
    173     dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
    174     dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
    175     dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
    176     dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
    177     dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
    178     dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
    179     dprintf(fd, "\nHISTOGRAM:");
    180     for (const auto& it : proto->histogram()) {
    181         dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
    182     }
    183     dprintf(fd, "\n");
    184 }
    185 
    186 void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
    187         int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
    188     service::GraphicsStatsProto statsProto;
    189     if (!parseFromFile(path, &statsProto)) {
    190         statsProto.Clear();
    191     }
    192     mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
    193     // Although we might not have read any data from the file, merging the existing data
    194     // should always fully-initialize the proto
    195     LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s",
    196             statsProto.InitializationErrorString().c_str());
    197     LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty()
    198             || !statsProto.has_summary(), "package_name() '%s' summary %d",
    199             statsProto.package_name().c_str(), statsProto.has_summary());
    200     int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
    201     if (outFd <= 0) {
    202         int err = errno;
    203         ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
    204         return;
    205     }
    206     int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
    207     if (wrote != sHeaderSize) {
    208         int err = errno;
    209         ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)",
    210                 path.c_str(), wrote, err, strerror(err));
    211         close(outFd);
    212         return;
    213     }
    214     {
    215         io::FileOutputStream output(outFd);
    216         bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
    217         if (output.GetErrno() != 0) {
    218             ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
    219                     outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno()));
    220             success = false;
    221         } else if (!success) {
    222             ALOGW("Serialize failed on '%s' unknown error", path.c_str());
    223         }
    224     }
    225     close(outFd);
    226 }
    227 
    228 class GraphicsStatsService::Dump {
    229 public:
    230     Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
    231     int fd() { return mFd; }
    232     DumpType type() { return mType; }
    233     service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
    234 private:
    235     int mFd;
    236     DumpType mType;
    237     service::GraphicsStatsServiceDumpProto mProto;
    238 };
    239 
    240 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
    241     return new Dump(outFd, type);
    242 }
    243 
    244 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,
    245         int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
    246     service::GraphicsStatsProto statsProto;
    247     if (!path.empty() && !parseFromFile(path, &statsProto)) {
    248         statsProto.Clear();
    249     }
    250     if (data) {
    251         mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
    252     }
    253     if (!statsProto.IsInitialized()) {
    254         ALOGW("Failed to load profile data from path '%s' and data %p",
    255                 path.empty() ? "<empty>" : path.c_str(), data);
    256         return;
    257     }
    258 
    259     if (dump->type() == DumpType::Protobuf) {
    260         dump->proto().add_stats()->CopyFrom(statsProto);
    261     } else {
    262         dumpAsTextToFd(&statsProto, dump->fd());
    263     }
    264 }
    265 
    266 void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
    267     service::GraphicsStatsProto statsProto;
    268     if (!parseFromFile(path, &statsProto)) {
    269         return;
    270     }
    271     if (dump->type() == DumpType::Protobuf) {
    272         dump->proto().add_stats()->CopyFrom(statsProto);
    273     } else {
    274         dumpAsTextToFd(&statsProto, dump->fd());
    275     }
    276 }
    277 
    278 void GraphicsStatsService::finishDump(Dump* dump) {
    279     if (dump->type() == DumpType::Protobuf) {
    280         io::FileOutputStream stream(dump->fd());
    281         dump->proto().SerializeToZeroCopyStream(&stream);
    282     }
    283     delete dump;
    284 }
    285 
    286 } /* namespace uirenderer */
    287 } /* namespace android */