/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "SurfaceFlingerPuller"

#include "SurfaceFlingerPuller.h"

#include <gui/SurfaceComposerClient.h>
#include <log/log.h>
#include <statslog.h>
#include <timestatsatomsproto/TimeStatsAtomsProtoHeader.h>

#include <vector>

namespace android {
namespace server {
namespace stats {

using android::util::BytesField;
using std::optional;

namespace {
optional<BytesField> getBytes(const google::protobuf::MessageLite& proto, std::string& data) {
    if (!proto.SerializeToString(&data)) {
        ALOGW("Unable to serialize surface flinger bytes field");
        return std::nullopt;
    }
    return {BytesField(data.data(), data.size())};
}
} // namespace

AStatsManager_PullAtomCallbackReturn SurfaceFlingerPuller::pull(int32_t atomTag,
                                                                AStatsEventList* data) {
    // Don't need mutexes here, since there is no global state.
    // SurfaceComposerClient is thread safe, and surfaceflinger is internally thread safe.

    bool success = false;
    std::string pullDataProto;
    status_t err = SurfaceComposerClient::onPullAtom(atomTag, &pullDataProto, &success);
    if (!success || err != NO_ERROR) {
        ALOGW("Failed to pull atom %" PRId32
              " from surfaceflinger. Success is %d, binder status is %s",
              atomTag, (int)success, binder::Status::exceptionToString(err).c_str());
        return AStatsManager_PULL_SKIP;
    }

    switch (atomTag) {
        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
            return parseGlobalInfoPull(pullDataProto, data);
        case android::util::SURFACEFLINGER_STATS_LAYER_INFO:
            return parseLayerInfoPull(pullDataProto, data);
        default:
            ALOGW("Invalid atom id for surfaceflinger pullers: %" PRId32, atomTag);
            return AStatsManager_PULL_SKIP;
    }
}

AStatsManager_PullAtomCallbackReturn SurfaceFlingerPuller::parseGlobalInfoPull(
        const std::string& protoData, AStatsEventList* data) {
    android::surfaceflinger::SurfaceflingerStatsGlobalInfoWrapper atomList;
    if (!atomList.ParseFromString(protoData)) {
        ALOGW("Error parsing surface flinger global stats to proto");
        return AStatsManager_PULL_SKIP;
    }

    for (const auto& atom : atomList.atom()) {
        // The strings must outlive the BytesFields, which only have a pointer to the data.
        std::string frameDurationStr, renderEngineTimeStr, deadlineMissesStr, predictionErrorsStr;
        optional<BytesField> frameDuration = getBytes(atom.frame_duration(), frameDurationStr);
        optional<BytesField> renderEngineTime =
                getBytes(atom.render_engine_timing(), renderEngineTimeStr);
        optional<BytesField> deadlineMisses =
                getBytes(atom.sf_deadline_misses(), deadlineMissesStr);
        optional<BytesField> predictionErrors =
                getBytes(atom.sf_prediction_errors(), predictionErrorsStr);

        // Fail if any serialization to bytes failed.
        if (!frameDuration || !renderEngineTime || !deadlineMisses || !predictionErrors) {
            return AStatsManager_PULL_SKIP;
        }

        android::util::addAStatsEvent(data, android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
                                      atom.total_frames(), atom.missed_frames(),
                                      atom.client_composition_frames(), atom.display_on_millis(),
                                      atom.animation_millis(), atom.event_connection_count(),
                                      frameDuration.value(), renderEngineTime.value(),
                                      atom.total_timeline_frames(), atom.total_janky_frames(),
                                      atom.total_janky_frames_with_long_cpu(),
                                      atom.total_janky_frames_with_long_gpu(),
                                      atom.total_janky_frames_sf_unattributed(),
                                      atom.total_janky_frames_app_unattributed(),
                                      atom.total_janky_frames_sf_scheduling(),
                                      atom.total_jank_frames_sf_prediction_error(),
                                      atom.total_jank_frames_app_buffer_stuffing(),
                                      atom.display_refresh_rate_bucket(), deadlineMisses.value(),
                                      predictionErrors.value(), atom.render_rate_bucket());
    }
    return AStatsManager_PULL_SUCCESS;
}

AStatsManager_PullAtomCallbackReturn SurfaceFlingerPuller::parseLayerInfoPull(
        const std::string& protoData, AStatsEventList* data) {
    android::surfaceflinger::SurfaceflingerStatsLayerInfoWrapper atomList;
    if (!atomList.ParseFromString(protoData)) {
        ALOGW("Error parsing surface flinger layer stats to proto");
        return AStatsManager_PULL_SKIP;
    }

    for (const auto& atom : atomList.atom()) {
        // The strings must outlive the BytesFields, which only have a pointer to the data.
        std::string present2PresentStr, post2presentStr, acquire2PresentStr, latch2PresentStr,
                desired2PresentStr, post2AcquireStr, frameRateVoteStr, appDeadlineMissesStr,
                present2PresentDeltaStr;
        optional<BytesField> present2Present =
                getBytes(atom.present_to_present(), present2PresentStr);
        optional<BytesField> present2PresentDelta =
                getBytes(atom.present_to_present_delta(), present2PresentDeltaStr);
        optional<BytesField> post2present = getBytes(atom.post_to_present(), post2presentStr);
        optional<BytesField> acquire2Present =
                getBytes(atom.acquire_to_present(), acquire2PresentStr);
        optional<BytesField> latch2Present = getBytes(atom.latch_to_present(), latch2PresentStr);
        optional<BytesField> desired2Present =
                getBytes(atom.desired_to_present(), desired2PresentStr);
        optional<BytesField> post2Acquire = getBytes(atom.post_to_acquire(), post2AcquireStr);
        optional<BytesField> frameRateVote = getBytes(atom.set_frame_rate_vote(), frameRateVoteStr);
        optional<BytesField> appDeadlineMisses =
                getBytes(atom.app_deadline_misses(), appDeadlineMissesStr);

        // Fail if any serialization to bytes failed.
        if (!present2Present || !post2present || !acquire2Present || !latch2Present ||
            !desired2Present || !post2Acquire || !frameRateVote || !appDeadlineMisses ||
            !present2PresentDelta) {
            return AStatsManager_PULL_SKIP;
        }

        android::util::addAStatsEvent(data, android::util::SURFACEFLINGER_STATS_LAYER_INFO,
                                      atom.layer_name().c_str(), atom.total_frames(),
                                      atom.dropped_frames(), present2Present.value(),
                                      post2present.value(), acquire2Present.value(),
                                      latch2Present.value(), desired2Present.value(),
                                      post2Acquire.value(), atom.late_acquire_frames(),
                                      atom.bad_desired_present_frames(), atom.uid(),
                                      atom.total_timeline_frames(), atom.total_janky_frames(),
                                      atom.total_janky_frames_with_long_cpu(),
                                      atom.total_janky_frames_with_long_gpu(),
                                      atom.total_janky_frames_sf_unattributed(),
                                      atom.total_janky_frames_app_unattributed(),
                                      atom.total_janky_frames_sf_scheduling(),
                                      atom.total_jank_frames_sf_prediction_error(),
                                      atom.total_jank_frames_app_buffer_stuffing(),
                                      atom.display_refresh_rate_bucket(), atom.render_rate_bucket(),
                                      frameRateVote.value(), appDeadlineMisses.value(),
                                      atom.game_mode(), present2PresentDelta.value());
    }
    return AStatsManager_PULL_SUCCESS;
}

} // namespace stats
} // namespace server
} // namespace android
