/*
 * Copyright (C) 2022 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.
 */

#include "src/trace_processor/importers/ftrace/drm_tracker.h"
#include "perfetto/ext/base/string_utils.h"
#include "protos/perfetto/trace/ftrace/dma_fence.pbzero.h"
#include "protos/perfetto/trace/ftrace/drm.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/gpu_scheduler.pbzero.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"

namespace perfetto {
namespace trace_processor {

namespace {

// There are meta-fences such as fence arrays or fence chains where a fence is
// a container of other fences.  These fences are on "unbound" timelines which
// are often dynamically created.  We want to ignore these timelines to avoid
// having tons of tracks for them.
constexpr char kUnboundFenceTimeline[] = "unbound";

}  // namespace

DrmTracker::DrmTracker(TraceProcessorContext* context)
    : context_(context),
      vblank_slice_signal_id_(context->storage->InternString("signal")),
      vblank_slice_deliver_id_(context->storage->InternString("deliver")),
      vblank_arg_seqno_id_(context->storage->InternString("vblank seqno")),
      sched_slice_schedule_id_(context->storage->InternString("drm_sched_job")),
      sched_slice_job_id_(context->storage->InternString("job")),
      sched_arg_ring_id_(context->storage->InternString("gpu sched ring")),
      sched_arg_job_id_(context->storage->InternString("gpu sched job")),
      fence_slice_fence_id_(context->storage->InternString("fence")),
      fence_slice_wait_id_(context->storage->InternString("dma_fence_wait")),
      fence_arg_context_id_(context->storage->InternString("fence context")),
      fence_arg_seqno_id_(context->storage->InternString("fence seqno")) {}

void DrmTracker::ParseDrm(int64_t timestamp,
                          uint32_t field_id,
                          uint32_t pid,
                          protozero::ConstBytes blob) {
  using protos::pbzero::FtraceEvent;

  switch (field_id) {
    case FtraceEvent::kDrmVblankEventFieldNumber: {
      protos::pbzero::DrmVblankEventFtraceEvent::Decoder evt(blob.data,
                                                             blob.size);
      DrmVblankEvent(timestamp, evt.crtc(), evt.seq());
      break;
    }
    case FtraceEvent::kDrmVblankEventDeliveredFieldNumber: {
      protos::pbzero::DrmVblankEventDeliveredFtraceEvent::Decoder evt(
          blob.data, blob.size);
      DrmVblankEventDelivered(timestamp, evt.crtc(), evt.seq());
      break;
    }

    case FtraceEvent::kDrmSchedJobFieldNumber: {
      protos::pbzero::DrmSchedJobFtraceEvent::Decoder evt(blob.data, blob.size);
      DrmSchedJob(timestamp, pid, evt.name(), evt.id());
      break;
    }
    case FtraceEvent::kDrmRunJobFieldNumber: {
      protos::pbzero::DrmRunJobFtraceEvent::Decoder evt(blob.data, blob.size);
      DrmRunJob(timestamp, evt.name(), evt.id(), evt.fence());
      break;
    }
    case FtraceEvent::kDrmSchedProcessJobFieldNumber: {
      protos::pbzero::DrmSchedProcessJobFtraceEvent::Decoder evt(blob.data,
                                                                 blob.size);
      DrmSchedProcessJob(timestamp, evt.fence());
      break;
    }
    case FtraceEvent::kDmaFenceInitFieldNumber: {
      protos::pbzero::DmaFenceInitFtraceEvent::Decoder evt(blob.data,
                                                           blob.size);
      DmaFenceInit(timestamp, evt.timeline(), evt.context(), evt.seqno());
      break;
    }
    case FtraceEvent::kDmaFenceEmitFieldNumber: {
      protos::pbzero::DmaFenceEmitFtraceEvent::Decoder evt(blob.data,
                                                           blob.size);
      DmaFenceEmit(timestamp, evt.timeline(), evt.context(), evt.seqno());
      break;
    }
    case FtraceEvent::kDmaFenceSignaledFieldNumber: {
      protos::pbzero::DmaFenceSignaledFtraceEvent::Decoder evt(blob.data,
                                                               blob.size);
      DmaFenceSignaled(timestamp, evt.timeline(), evt.context(), evt.seqno());
      break;
    }
    case FtraceEvent::kDmaFenceWaitStartFieldNumber: {
      protos::pbzero::DmaFenceWaitStartFtraceEvent::Decoder evt(blob.data,
                                                                blob.size);
      DmaFenceWaitStart(timestamp, pid, evt.context(), evt.seqno());
      break;
    }
    case FtraceEvent::kDmaFenceWaitEndFieldNumber: {
      DmaFenceWaitEnd(timestamp, pid);
      break;
    }
    default:
      PERFETTO_DFATAL("Unexpected field id");
      break;
  }
}

TrackId DrmTracker::InternVblankTrack(int32_t crtc) {
  base::StackString<256> track_name("vblank-%d", crtc);
  StringId track_name_id =
      context_->storage->InternString(track_name.string_view());
  return context_->track_tracker->LegacyInternGpuTrack(
      tables::GpuTrackTable::Row(track_name_id));
}

void DrmTracker::DrmVblankEvent(int64_t timestamp,
                                int32_t crtc,
                                uint32_t seqno) {
  TrackId track_id = InternVblankTrack(crtc);
  auto args_inserter = [this, seqno](ArgsTracker::BoundInserter* inserter) {
    inserter->AddArg(vblank_arg_seqno_id_, Variadic::UnsignedInteger(seqno));
  };

  context_->slice_tracker->Scoped(timestamp, track_id, kNullStringId,
                                  vblank_slice_signal_id_, 0, args_inserter);
}

void DrmTracker::DrmVblankEventDelivered(int64_t timestamp,
                                         int32_t crtc,
                                         uint32_t seqno) {
  TrackId track_id = InternVblankTrack(crtc);
  auto args_inserter = [this, seqno](ArgsTracker::BoundInserter* inserter) {
    inserter->AddArg(vblank_arg_seqno_id_, Variadic::UnsignedInteger(seqno));
  };

  context_->slice_tracker->Scoped(timestamp, track_id, kNullStringId,
                                  vblank_slice_deliver_id_, 0, args_inserter);
}

DrmTracker::SchedRing& DrmTracker::GetSchedRingByName(base::StringView name) {
  auto* iter = sched_rings_.Find(name);
  if (iter)
    return **iter;

  // intern a gpu track
  base::StackString<64> track_name("sched-%.*s", int(name.size()), name.data());
  StringId track_name_id =
      context_->storage->InternString(track_name.string_view());
  TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(
      tables::GpuTrackTable::Row(track_name_id));

  // no std::make_unique until C++14..
  auto ring = std::unique_ptr<SchedRing>(new SchedRing());
  ring->track_id = track_id;

  SchedRing& ret = *ring;
  sched_rings_.Insert(name, std::move(ring));

  return ret;
}

void DrmTracker::BeginSchedRingSlice(int64_t timestamp, SchedRing& ring) {
  PERFETTO_DCHECK(!ring.running_jobs.empty());
  uint64_t job_id = ring.running_jobs.front();

  auto args_inserter = [this, job_id](ArgsTracker::BoundInserter* inserter) {
    inserter->AddArg(sched_arg_job_id_, Variadic::UnsignedInteger(job_id));
  };

  std::optional<SliceId> slice_id =
      context_->slice_tracker->Begin(timestamp, ring.track_id, kNullStringId,
                                     sched_slice_job_id_, args_inserter);

  if (slice_id) {
    SliceId* out_slice_id = ring.out_slice_ids.Find(job_id);
    if (out_slice_id) {
      context_->flow_tracker->InsertFlow(*out_slice_id, *slice_id);
      ring.out_slice_ids.Erase(job_id);
    }
  }
}

void DrmTracker::DrmSchedJob(int64_t timestamp,
                             uint32_t pid,
                             base::StringView name,
                             uint64_t job_id) {
  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
  StringId ring_id = context_->storage->InternString(name);
  auto args_inserter = [this, ring_id,
                        job_id](ArgsTracker::BoundInserter* inserter) {
    inserter->AddArg(sched_arg_ring_id_, Variadic::String(ring_id));
    inserter->AddArg(sched_arg_job_id_, Variadic::UnsignedInteger(job_id));
  };

  std::optional<SliceId> slice_id = context_->slice_tracker->Scoped(
      timestamp, track_id, kNullStringId, sched_slice_schedule_id_, 0,
      args_inserter);

  if (slice_id) {
    SchedRing& ring = GetSchedRingByName(name);
    ring.out_slice_ids[job_id] = *slice_id;
  }
}

void DrmTracker::DrmRunJob(int64_t timestamp,
                           base::StringView name,
                           uint64_t job_id,
                           uint64_t fence_id) {
  SchedRing& ring = GetSchedRingByName(name);

  ring.running_jobs.push_back(job_id);
  sched_pending_fences_.Insert(fence_id, &ring);

  if (ring.running_jobs.size() == 1)
    BeginSchedRingSlice(timestamp, ring);
}

void DrmTracker::DrmSchedProcessJob(int64_t timestamp, uint64_t fence_id) {
  // look up ring using fence_id
  auto* iter = sched_pending_fences_.Find(fence_id);
  if (!iter)
    return;
  SchedRing& ring = **iter;
  sched_pending_fences_.Erase(fence_id);

  ring.running_jobs.pop_front();
  context_->slice_tracker->End(timestamp, ring.track_id);

  if (!ring.running_jobs.empty())
    BeginSchedRingSlice(timestamp, ring);
}

DrmTracker::FenceTimeline& DrmTracker::GetFenceTimelineByContext(
    uint32_t context,
    base::StringView name) {
  auto* iter = fence_timelines_.Find(context);
  if (iter)
    return **iter;

  // intern a gpu track
  base::StackString<64> track_name("fence-%.*s-%u", int(name.size()),
                                   name.data(), context);
  StringId track_name_id =
      context_->storage->InternString(track_name.string_view());
  TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(
      tables::GpuTrackTable::Row(track_name_id));

  // no std::make_unique until C++14..
  auto timeline = std::unique_ptr<FenceTimeline>(new FenceTimeline());
  timeline->track_id = track_id;

  FenceTimeline& ret = *timeline;
  fence_timelines_.Insert(context, std::move(timeline));

  return ret;
}

void DrmTracker::BeginFenceTimelineSlice(int64_t timestamp,
                                         const FenceTimeline& timeline) {
  PERFETTO_DCHECK(!timeline.pending_fences.empty());
  uint32_t seqno = timeline.pending_fences.front();

  auto args_inserter = [this, seqno](ArgsTracker::BoundInserter* inserter) {
    inserter->AddArg(fence_arg_seqno_id_, Variadic::UnsignedInteger(seqno));
  };

  context_->slice_tracker->Begin(timestamp, timeline.track_id, kNullStringId,
                                 fence_slice_fence_id_, args_inserter);
}

void DrmTracker::DmaFenceInit(int64_t timestamp,
                              base::StringView name,
                              uint32_t context,
                              uint32_t seqno) {
  if (name == kUnboundFenceTimeline)
    return;

  FenceTimeline& timeline = GetFenceTimelineByContext(context, name);
  // ignore dma_fence_init when the timeline has dma_fence_emit
  if (timeline.has_dma_fence_emit)
    return;

  timeline.pending_fences.push_back(seqno);

  if (timeline.pending_fences.size() == 1)
    BeginFenceTimelineSlice(timestamp, timeline);
}

void DrmTracker::DmaFenceEmit(int64_t timestamp,
                              base::StringView name,
                              uint32_t context,
                              uint32_t seqno) {
  if (name == kUnboundFenceTimeline)
    return;

  FenceTimeline& timeline = GetFenceTimelineByContext(context, name);

  // Most timelines do not have dma_fence_emit and we rely on the less
  // accurate dma_fence_init instead.  But for those who do, we will switch to
  // dma_fence_emit.
  if (!timeline.has_dma_fence_emit) {
    timeline.has_dma_fence_emit = true;

    if (!timeline.pending_fences.empty()) {
      context_->slice_tracker->End(timestamp, timeline.track_id);
      timeline.pending_fences.clear();
    }
  }

  timeline.pending_fences.push_back(seqno);

  if (timeline.pending_fences.size() == 1)
    BeginFenceTimelineSlice(timestamp, timeline);
}

void DrmTracker::DmaFenceSignaled(int64_t timestamp,
                                  base::StringView name,
                                  uint32_t context,
                                  uint32_t seqno) {
  if (name == kUnboundFenceTimeline)
    return;

  FenceTimeline& timeline = GetFenceTimelineByContext(context, name);
  if (timeline.pending_fences.empty() ||
      seqno < timeline.pending_fences.front()) {
    return;
  }

  timeline.pending_fences.pop_front();
  context_->slice_tracker->End(timestamp, timeline.track_id);

  if (!timeline.pending_fences.empty())
    BeginFenceTimelineSlice(timestamp, timeline);
}

void DrmTracker::DmaFenceWaitStart(int64_t timestamp,
                                   uint32_t pid,
                                   uint32_t context,
                                   uint32_t seqno) {
  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
  auto args_inserter = [this, context,
                        seqno](ArgsTracker::BoundInserter* inserter) {
    inserter->AddArg(fence_arg_context_id_, Variadic::UnsignedInteger(context));
    inserter->AddArg(fence_arg_seqno_id_, Variadic::UnsignedInteger(seqno));
  };

  context_->slice_tracker->Begin(timestamp, track_id, kNullStringId,
                                 fence_slice_wait_id_, args_inserter);
}

void DrmTracker::DmaFenceWaitEnd(int64_t timestamp, uint32_t pid) {
  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);

  context_->slice_tracker->End(timestamp, track_id);
}

}  // namespace trace_processor
}  // namespace perfetto
