Merge "trace_processor: support drm-related events"
diff --git a/Android.bp b/Android.bp
index 5373c69..a1c55ee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8572,6 +8572,7 @@
     srcs: [
         "src/trace_processor/importers/additional_modules.cc",
         "src/trace_processor/importers/ftrace/binder_tracker.cc",
+        "src/trace_processor/importers/ftrace/drm_tracker.cc",
         "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
         "src/trace_processor/importers/ftrace/ftrace_parser.cc",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
diff --git a/BUILD b/BUILD
index 8fa7874..da9ae80 100644
--- a/BUILD
+++ b/BUILD
@@ -1439,6 +1439,8 @@
         "src/trace_processor/importers/additional_modules.h",
         "src/trace_processor/importers/ftrace/binder_tracker.cc",
         "src/trace_processor/importers/ftrace/binder_tracker.h",
+        "src/trace_processor/importers/ftrace/drm_tracker.cc",
+        "src/trace_processor/importers/ftrace/drm_tracker.h",
         "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
         "src/trace_processor/importers/ftrace/ftrace_module_impl.h",
         "src/trace_processor/importers/ftrace/ftrace_parser.cc",
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index ec49f71..d40dcb6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -218,6 +218,8 @@
     "importers/additional_modules.h",
     "importers/ftrace/binder_tracker.cc",
     "importers/ftrace/binder_tracker.h",
+    "importers/ftrace/drm_tracker.cc",
+    "importers/ftrace/drm_tracker.h",
     "importers/ftrace/ftrace_module_impl.cc",
     "importers/ftrace/ftrace_module_impl.h",
     "importers/ftrace/ftrace_parser.cc",
diff --git a/src/trace_processor/importers/ftrace/drm_tracker.cc b/src/trace_processor/importers/ftrace/drm_tracker.cc
new file mode 100644
index 0000000..ebe5b31
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/drm_tracker.cc
@@ -0,0 +1,378 @@
+/*
+ * 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,
+                          int32_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->InternGpuTrack(
+      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->InternGpuTrack(
+      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));
+  };
+
+  base::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));
+  };
+
+  base::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->InternGpuTrack(
+      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
diff --git a/src/trace_processor/importers/ftrace/drm_tracker.h b/src/trace_processor/importers/ftrace/drm_tracker.h
new file mode 100644
index 0000000..3762df6
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/drm_tracker.h
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_DRM_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_DRM_TRACKER_H_
+
+#include <deque>
+#include <memory>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/field.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class DrmTracker {
+ public:
+  explicit DrmTracker(TraceProcessorContext*);
+
+  void ParseDrm(int64_t timestamp,
+                int32_t field_id,
+                uint32_t pid,
+                protozero::ConstBytes blob);
+
+ private:
+  TrackId InternVblankTrack(int32_t crtc);
+  void DrmVblankEvent(int64_t timestamp, int32_t crtc, uint32_t seqno);
+  void DrmVblankEventDelivered(int64_t timestamp, int32_t crtc, uint32_t seqno);
+
+  struct SchedRing {
+    TrackId track_id;
+    std::deque<uint64_t> running_jobs;
+
+    base::FlatHashMap<uint64_t, SliceId> out_slice_ids;
+  };
+  SchedRing& GetSchedRingByName(base::StringView name);
+  void BeginSchedRingSlice(int64_t timestamp, SchedRing& ring);
+
+  void DrmSchedJob(int64_t timestamp,
+                   uint32_t pid,
+                   base::StringView name,
+                   uint64_t job_id);
+  void DrmRunJob(int64_t timestamp,
+                 base::StringView name,
+                 uint64_t job_id,
+                 uint64_t fence_id);
+  void DrmSchedProcessJob(int64_t timestamp, uint64_t fence_id);
+
+  struct FenceTimeline {
+    TrackId track_id;
+    bool has_dma_fence_emit;
+    std::deque<uint32_t> pending_fences;
+  };
+  FenceTimeline& GetFenceTimelineByContext(uint32_t context,
+                                           base::StringView name);
+  void BeginFenceTimelineSlice(int64_t timestamp,
+                               const FenceTimeline& timeline);
+
+  void DmaFenceInit(int64_t timestamp,
+                    base::StringView name,
+                    uint32_t context,
+                    uint32_t seqno);
+  void DmaFenceEmit(int64_t timestamp,
+                    base::StringView name,
+                    uint32_t context,
+                    uint32_t seqno);
+  void DmaFenceSignaled(int64_t timestamp,
+                        base::StringView name,
+                        uint32_t context,
+                        uint32_t seqno);
+  void DmaFenceWaitStart(int64_t timestamp,
+                         uint32_t pid,
+                         uint32_t context,
+                         uint32_t seqno);
+  void DmaFenceWaitEnd(int64_t timestamp, uint32_t pid);
+
+  TraceProcessorContext* const context_;
+
+  const StringId vblank_slice_signal_id_;
+  const StringId vblank_slice_deliver_id_;
+  const StringId vblank_arg_seqno_id_;
+  const StringId sched_slice_schedule_id_;
+  const StringId sched_slice_job_id_;
+  const StringId sched_arg_ring_id_;
+  const StringId sched_arg_job_id_;
+  const StringId fence_slice_fence_id_;
+  const StringId fence_slice_wait_id_;
+  const StringId fence_arg_context_id_;
+  const StringId fence_arg_seqno_id_;
+
+  base::FlatHashMap<base::StringView, std::unique_ptr<SchedRing>> sched_rings_;
+  base::FlatHashMap<uint64_t, SchedRing*> sched_pending_fences_;
+
+  base::FlatHashMap<uint32_t, std::unique_ptr<FenceTimeline>> fence_timelines_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_DRM_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 4078319..3861e92 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -109,6 +109,7 @@
 FtraceParser::FtraceParser(TraceProcessorContext* context)
     : context_(context),
       rss_stat_tracker_(context),
+      drm_tracker_(context),
       sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
       sched_waking_name_id_(context->storage->InternString("sched_waking")),
       cpu_id_(context->storage->InternString("cpu")),
@@ -746,6 +747,19 @@
         ParseSuspendResume(ts, data);
         break;
       }
+      case FtraceEvent::kDrmVblankEventFieldNumber:
+      case FtraceEvent::kDrmVblankEventDeliveredFieldNumber:
+      case FtraceEvent::kDrmSchedJobFieldNumber:
+      case FtraceEvent::kDrmRunJobFieldNumber:
+      case FtraceEvent::kDrmSchedProcessJobFieldNumber:
+      case FtraceEvent::kDmaFenceInitFieldNumber:
+      case FtraceEvent::kDmaFenceEmitFieldNumber:
+      case FtraceEvent::kDmaFenceSignaledFieldNumber:
+      case FtraceEvent::kDmaFenceWaitStartFieldNumber:
+      case FtraceEvent::kDmaFenceWaitEndFieldNumber: {
+        drm_tracker_.ParseDrm(ts, fld.id(), pid, data);
+        break;
+      }
       default:
         break;
     }
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index e4730c6..a4ef6f3 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -19,6 +19,7 @@
 
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/ftrace/drm_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
 #include "src/trace_processor/importers/ftrace/rss_stat_tracker.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
@@ -179,6 +180,7 @@
 
   TraceProcessorContext* context_;
   RssStatTracker rss_stat_tracker_;
+  DrmTracker drm_tracker_;
 
   const StringId sched_wakeup_name_id_;
   const StringId sched_waking_name_id_;
diff --git a/test/trace_processor/graphics/drm_dma_fence.textproto b/test/trace_processor/graphics/drm_dma_fence.textproto
new file mode 100644
index 0000000..d7166cc
--- /dev/null
+++ b/test/trace_processor/graphics/drm_dma_fence.textproto
@@ -0,0 +1,150 @@
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 11303602488073
+      pid: 191
+      dma_fence_init {
+        context: 1
+        driver: "msm"
+        seqno: 16665
+        timeline: "gpu-ring-0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 11303602500886
+      pid: 191
+      dma_fence_emit {
+        context: 1
+        driver: "msm"
+        seqno: 16665
+        timeline: "gpu-ring-0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 11303604370470
+      pid: 191
+      dma_fence_init {
+        context: 1
+        driver: "msm"
+        seqno: 16666
+        timeline: "gpu-ring-0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 11303604372188
+      pid: 191
+      dma_fence_emit {
+        context: 1
+        driver: "msm"
+        seqno: 16666
+        timeline: "gpu-ring-0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 11303607306512
+      pid: 0
+      dma_fence_signaled {
+        context: 1
+        driver: "msm"
+        seqno: 16665
+        timeline: "gpu-ring-0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 11303611157295
+      pid: 0
+      dma_fence_signaled {
+        context: 1
+        driver: "msm"
+        seqno: 16666
+        timeline: "gpu-ring-0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 3
+    event {
+      timestamp: 11303702681699
+      pid: 365
+      dma_fence_init {
+        context: 9
+        driver: "drm_sched"
+        seqno: 5065
+        timeline: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 5
+    event {
+      timestamp: 11303702851231
+      pid: 144
+      dma_fence_wait_start {
+        context: 9
+        driver: "drm_sched"
+        seqno: 5065
+        timeline: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 11303707550086
+      pid: 0
+      dma_fence_signaled {
+        context: 9
+        driver: "drm_sched"
+        seqno: 5065
+        timeline: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 5
+    event {
+      timestamp: 11303707718889
+      pid: 144
+      dma_fence_wait_end {
+        context: 9
+        driver: "drm_sched"
+        seqno: 5065
+        timeline: "ring0"
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/graphics/drm_dma_fence_gpu_track.out b/test/trace_processor/graphics/drm_dma_fence_gpu_track.out
new file mode 100644
index 0000000..5ec5f6d
--- /dev/null
+++ b/test/trace_processor/graphics/drm_dma_fence_gpu_track.out
@@ -0,0 +1,5 @@
+"name","ts","dur","name","flat_key","int_value","string_value"
+"fence-gpu-ring-0-1",11303602488073,12813,"fence","fence seqno",16665,"[NULL]"
+"fence-gpu-ring-0-1",11303602500886,4805626,"fence","fence seqno",16665,"[NULL]"
+"fence-gpu-ring-0-1",11303607306512,3850783,"fence","fence seqno",16666,"[NULL]"
+"fence-ring0-9",11303702681699,4868387,"fence","fence seqno",5065,"[NULL]"
diff --git a/test/trace_processor/graphics/drm_dma_fence_thread_track.out b/test/trace_processor/graphics/drm_dma_fence_thread_track.out
new file mode 100644
index 0000000..c4d5bda
--- /dev/null
+++ b/test/trace_processor/graphics/drm_dma_fence_thread_track.out
@@ -0,0 +1,3 @@
+"utid","ts","dur","name","flat_key","int_value","string_value"
+3,11303702851231,4867658,"dma_fence_wait","fence context",9,"[NULL]"
+3,11303702851231,4867658,"dma_fence_wait","fence seqno",5065,"[NULL]"
diff --git a/test/trace_processor/graphics/drm_gpu_track.sql b/test/trace_processor/graphics/drm_gpu_track.sql
new file mode 100644
index 0000000..b1554a8
--- /dev/null
+++ b/test/trace_processor/graphics/drm_gpu_track.sql
@@ -0,0 +1,15 @@
+SELECT
+  gpu_track.name,
+  ts,
+  dur,
+  slice.name,
+  flat_key,
+  int_value,
+  string_value
+FROM
+  gpu_track
+  JOIN slice
+  ON slice.track_id = gpu_track.id
+  JOIN args
+  ON slice.arg_set_id = args.arg_set_id
+ORDER BY ts
diff --git a/test/trace_processor/graphics/drm_sched.textproto b/test/trace_processor/graphics/drm_sched.textproto
new file mode 100644
index 0000000..7ce3528
--- /dev/null
+++ b/test/trace_processor/graphics/drm_sched.textproto
@@ -0,0 +1,184 @@
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 9246165326050
+      pid: 365
+      drm_sched_job {
+        entity: 18446743526218327296
+        fence: 18446743526909918272
+        hw_job_count: 0
+        id: 13481
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 9246165349383
+      pid: 191
+      drm_run_job {
+        entity: 18446743526218327296
+        fence: 18446743526909918272
+        hw_job_count: 1
+        id: 13481
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 4
+    event {
+      timestamp: 9246166957616
+      pid: 762
+      drm_sched_job {
+        entity: 18446743526854133248
+        fence: 18446743526610453312
+        hw_job_count: 1
+        id: 13482
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 9246166978033
+      pid: 191
+      drm_run_job {
+        entity: 18446743526854133248
+        fence: 18446743526610453312
+        hw_job_count: 2
+        id: 13482
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 4
+    event {
+      timestamp: 9246167272512
+      pid: 762
+      drm_sched_job {
+        entity: 18446743526854133248
+        fence: 18446743526610452736
+        hw_job_count: 2
+        id: 13483
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 9246167288190
+      pid: 191
+      drm_run_job {
+        entity: 18446743526854133248
+        fence: 18446743526610452736
+        hw_job_count: 3
+        id: 13483
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 9246170078456
+      pid: 0
+      drm_sched_process_job {
+        fence: 18446743526909918272
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 9246174020027
+      pid: 0
+      drm_sched_process_job {
+        fence: 18446743526610453312
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 9246174045183
+      pid: 0
+      drm_sched_process_job {
+        fence: 18446743526610452736
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 9246181907439
+      pid: 365
+      drm_sched_job {
+        entity: 18446743526218327296
+        fence: 18446743526909915584
+        hw_job_count: 0
+        id: 13484
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 7
+    event {
+      timestamp: 9246181933273
+      pid: 191
+      drm_run_job {
+        entity: 18446743526218327296
+        fence: 18446743526909915584
+        hw_job_count: 1
+        id: 13484
+        job_count: 0
+        name: "ring0"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 9246186659585
+      pid: 0
+      drm_sched_process_job {
+        fence: 18446743526909915584
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/graphics/drm_sched_gpu_track.out b/test/trace_processor/graphics/drm_sched_gpu_track.out
new file mode 100644
index 0000000..e9533da
--- /dev/null
+++ b/test/trace_processor/graphics/drm_sched_gpu_track.out
@@ -0,0 +1,5 @@
+"name","ts","dur","name","flat_key","int_value","string_value"
+"sched-ring0",9246165349383,4729073,"job","gpu sched job",13481,"[NULL]"
+"sched-ring0",9246170078456,3941571,"job","gpu sched job",13482,"[NULL]"
+"sched-ring0",9246174020027,25156,"job","gpu sched job",13483,"[NULL]"
+"sched-ring0",9246181933273,4726312,"job","gpu sched job",13484,"[NULL]"
diff --git a/test/trace_processor/graphics/drm_sched_thread_track.out b/test/trace_processor/graphics/drm_sched_thread_track.out
new file mode 100644
index 0000000..6e9e577
--- /dev/null
+++ b/test/trace_processor/graphics/drm_sched_thread_track.out
@@ -0,0 +1,9 @@
+"utid","ts","dur","name","flat_key","int_value","string_value"
+1,9246165326050,0,"drm_sched_job","gpu sched ring","[NULL]","ring0"
+1,9246165326050,0,"drm_sched_job","gpu sched job",13481,"[NULL]"
+3,9246166957616,0,"drm_sched_job","gpu sched ring","[NULL]","ring0"
+3,9246166957616,0,"drm_sched_job","gpu sched job",13482,"[NULL]"
+3,9246167272512,0,"drm_sched_job","gpu sched ring","[NULL]","ring0"
+3,9246167272512,0,"drm_sched_job","gpu sched job",13483,"[NULL]"
+1,9246181907439,0,"drm_sched_job","gpu sched ring","[NULL]","ring0"
+1,9246181907439,0,"drm_sched_job","gpu sched job",13484,"[NULL]"
diff --git a/test/trace_processor/graphics/drm_thread_track.sql b/test/trace_processor/graphics/drm_thread_track.sql
new file mode 100644
index 0000000..4c879fb
--- /dev/null
+++ b/test/trace_processor/graphics/drm_thread_track.sql
@@ -0,0 +1,15 @@
+SELECT
+  utid,
+  ts,
+  dur,
+  slice.name,
+  flat_key,
+  int_value,
+  string_value
+FROM
+  thread_track
+  JOIN slice
+  ON slice.track_id = thread_track.id
+  JOIN args
+  ON slice.arg_set_id = args.arg_set_id
+ORDER BY ts
diff --git a/test/trace_processor/graphics/drm_vblank.textproto b/test/trace_processor/graphics/drm_vblank.textproto
new file mode 100644
index 0000000..71817fc
--- /dev/null
+++ b/test/trace_processor/graphics/drm_vblank.textproto
@@ -0,0 +1,29 @@
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 6159770881976
+      pid: 0
+      drm_vblank_event {
+        crtc: 0
+        high_prec: 1
+        seq: 3551
+        time: 6159771267407
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 4
+    event {
+      timestamp: 6159770993376
+      pid: 144
+      drm_vblank_event_delivered {
+        crtc: 0
+        file: 18446743526216291840
+        seq: 3551
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/graphics/drm_vblank_gpu_track.out b/test/trace_processor/graphics/drm_vblank_gpu_track.out
new file mode 100644
index 0000000..1578c8e
--- /dev/null
+++ b/test/trace_processor/graphics/drm_vblank_gpu_track.out
@@ -0,0 +1,3 @@
+"name","ts","dur","name","flat_key","int_value","string_value"
+"vblank-0",6159770881976,0,"signal","vblank seqno",3551,"[NULL]"
+"vblank-0",6159770993376,0,"deliver","vblank seqno",3551,"[NULL]"
diff --git a/test/trace_processor/graphics/index b/test/trace_processor/graphics/index
index 34cc136..603db06 100644
--- a/test/trace_processor/graphics/index
+++ b/test/trace_processor/graphics/index
@@ -52,3 +52,10 @@
 
 # DPU vote clock and bandwidth
 dpu_vote_clock_bw.textproto android_hwcomposer dpu_vote_clock_bw.out
+
+# DRM-related ftrace events
+drm_vblank.textproto drm_gpu_track.sql drm_vblank_gpu_track.out
+drm_sched.textproto drm_gpu_track.sql drm_sched_gpu_track.out
+drm_sched.textproto drm_thread_track.sql drm_sched_thread_track.out
+drm_dma_fence.textproto drm_gpu_track.sql drm_dma_fence_gpu_track.out
+drm_dma_fence.textproto drm_thread_track.sql drm_dma_fence_thread_track.out