blob: 003b82795e87575f97d80eb0ef25ddffdac31bcc [file] [log] [blame]
/*
* 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));
};
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->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