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
31namespace android {
32namespace uirenderer {
33
34using namespace google::protobuf;
35
36constexpr int32_t sCurrentFileVersion = 1;
37constexpr int32_t sHeaderSize = 4;
38static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
39
40constexpr int sHistogramSize =
41        std::tuple_size<decltype(ProfileData::frameCounts)>::value +
42        std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value;
43
44static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
45        const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
46        const ProfileData* data);
47static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
48
49bool 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
84void 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
145static 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
157void 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
186void 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
228class GraphicsStatsService::Dump {
229public:
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; }
234private:
235    int mFd;
236    DumpType mType;
237    service::GraphicsStatsServiceDumpProto mProto;
238};
239
240GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
241    return new Dump(outFd, type);
242}
243
244void 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
266void 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
278void 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 */