/*
 * Copyright 2019 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.
 */

// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"

#undef LOG_TAG
#define LOG_TAG "FrameTracer"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include "FrameTracer.h"

#include <android-base/stringprintf.h>
#include <perfetto/common/builtin_clock.pbzero.h>

#include <algorithm>
#include <mutex>

PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::FrameTracer::FrameTracerDataSource);

namespace android {

void FrameTracer::initialize() {
    std::call_once(mInitializationFlag, [this]() {
        perfetto::TracingInitArgs args;
        args.backends = perfetto::kSystemBackend;
        perfetto::Tracing::Initialize(args);
        registerDataSource();
    });
}

void FrameTracer::registerDataSource() {
    perfetto::DataSourceDescriptor dsd;
    dsd.set_name(kFrameTracerDataSource);
    FrameTracerDataSource::Register(dsd);
}

void FrameTracer::traceNewLayer(int32_t layerId, const std::string& layerName) {
    FrameTracerDataSource::Trace([this, layerId, &layerName](FrameTracerDataSource::TraceContext) {
        if (mTraceTracker.find(layerId) == mTraceTracker.end()) {
            std::lock_guard<std::mutex> lock(mTraceMutex);
            mTraceTracker[layerId].layerName = layerName;
        }
    });
}

void FrameTracer::traceTimestamp(int32_t layerId, uint64_t bufferID, uint64_t frameNumber,
                                 nsecs_t timestamp, FrameEvent::BufferEventType type,
                                 nsecs_t duration) {
    FrameTracerDataSource::Trace([this, layerId, bufferID, frameNumber, timestamp, type,
                                  duration](FrameTracerDataSource::TraceContext ctx) {
        std::lock_guard<std::mutex> lock(mTraceMutex);
        if (mTraceTracker.find(layerId) == mTraceTracker.end()) {
            return;
        }

        // Handle any pending fences for this buffer.
        tracePendingFencesLocked(ctx, layerId, bufferID);

        // Complete current trace.
        traceLocked(ctx, layerId, bufferID, frameNumber, timestamp, type, duration);
    });
}

void FrameTracer::traceFence(int32_t layerId, uint64_t bufferID, uint64_t frameNumber,
                             const std::shared_ptr<FenceTime>& fence,
                             FrameEvent::BufferEventType type, nsecs_t startTime) {
    FrameTracerDataSource::Trace([this, layerId, bufferID, frameNumber, &fence, type,
                                  startTime](FrameTracerDataSource::TraceContext ctx) {
        const nsecs_t signalTime = fence->getSignalTime();
        if (signalTime != Fence::SIGNAL_TIME_INVALID) {
            std::lock_guard<std::mutex> lock(mTraceMutex);
            if (mTraceTracker.find(layerId) == mTraceTracker.end()) {
                return;
            }

            // Handle any pending fences for this buffer.
            tracePendingFencesLocked(ctx, layerId, bufferID);

            if (signalTime != Fence::SIGNAL_TIME_PENDING) {
                traceSpanLocked(ctx, layerId, bufferID, frameNumber, type, startTime, signalTime);
            } else {
                mTraceTracker[layerId].pendingFences[bufferID].push_back(
                        {.frameNumber = frameNumber,
                         .type = type,
                         .fence = fence,
                         .startTime = startTime});
            }
        }
    });
}

void FrameTracer::tracePendingFencesLocked(FrameTracerDataSource::TraceContext& ctx,
                                           int32_t layerId, uint64_t bufferID) {
    if (mTraceTracker[layerId].pendingFences.count(bufferID)) {
        auto& pendingFences = mTraceTracker[layerId].pendingFences[bufferID];
        for (size_t i = 0; i < pendingFences.size(); ++i) {
            auto& pendingFence = pendingFences[i];

            nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
            if (pendingFence.fence && pendingFence.fence->isValid()) {
                signalTime = pendingFence.fence->getSignalTime();
                if (signalTime == Fence::SIGNAL_TIME_PENDING) {
                    continue;
                }
            }

            if (signalTime != Fence::SIGNAL_TIME_INVALID &&
                systemTime() - signalTime < kFenceSignallingDeadline) {
                traceSpanLocked(ctx, layerId, bufferID, pendingFence.frameNumber, pendingFence.type,
                                pendingFence.startTime, signalTime);
            }

            pendingFences.erase(pendingFences.begin() + i);
            --i;
        }
    }
}

void FrameTracer::traceLocked(FrameTracerDataSource::TraceContext& ctx, int32_t layerId,
                              uint64_t bufferID, uint64_t frameNumber, nsecs_t timestamp,
                              FrameEvent::BufferEventType type, nsecs_t duration) {
    auto packet = ctx.NewTracePacket();
    packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
    packet->set_timestamp(timestamp);
    auto* event = packet->set_graphics_frame_event()->set_buffer_event();
    event->set_buffer_id(static_cast<uint32_t>(bufferID));
    if (frameNumber != UNSPECIFIED_FRAME_NUMBER) {
        event->set_frame_number(frameNumber);
    }
    event->set_type(type);

    if (mTraceTracker.find(layerId) != mTraceTracker.end() &&
        !mTraceTracker[layerId].layerName.empty()) {
        const std::string& layerName = mTraceTracker[layerId].layerName;
        event->set_layer_name(layerName.c_str(), layerName.size());
    }

    if (duration > 0) {
        event->set_duration_ns(duration);
    }
}

void FrameTracer::traceSpanLocked(FrameTracerDataSource::TraceContext& ctx, int32_t layerId,
                                  uint64_t bufferID, uint64_t frameNumber,
                                  FrameEvent::BufferEventType type, nsecs_t startTime,
                                  nsecs_t endTime) {
    nsecs_t timestamp = endTime;
    nsecs_t duration = 0;
    if (startTime > 0 && startTime < endTime) {
        timestamp = startTime;
        duration = endTime - startTime;
    }
    traceLocked(ctx, layerId, bufferID, frameNumber, timestamp, type, duration);
}

void FrameTracer::onDestroy(int32_t layerId) {
    std::lock_guard<std::mutex> traceLock(mTraceMutex);
    mTraceTracker.erase(layerId);
}

std::string FrameTracer::miniDump() {
    std::string result = "FrameTracer miniDump:\n";
    std::lock_guard<std::mutex> lock(mTraceMutex);
    android::base::StringAppendF(&result, "Number of layers currently being traced is %zu\n",
                                 mTraceTracker.size());
    return result;
}

} // namespace android

// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion"
