Path: blob/main/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.cpp
39645 views
//===-- PerfContextSwitchDecoder.cpp --======------------------------------===//1// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.2// See https://llvm.org/LICENSE.txt for license information.3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception4//5//===----------------------------------------------------------------------===//67#include "PerfContextSwitchDecoder.h"8#include <optional>910using namespace lldb;11using namespace lldb_private;12using namespace lldb_private::trace_intel_pt;13using namespace llvm;1415/// Copied from <linux/perf_event.h> to avoid depending on perf_event.h on16/// non-linux platforms.17/// \{18#define PERF_RECORD_MISC_SWITCH_OUT (1 << 13)1920#define PERF_RECORD_LOST 221#define PERF_RECORD_THROTTLE 522#define PERF_RECORD_UNTHROTTLE 623#define PERF_RECORD_LOST_SAMPLES 1324#define PERF_RECORD_SWITCH_CPU_WIDE 1525#define PERF_RECORD_MAX 192627struct perf_event_header {28uint32_t type;29uint16_t misc;30uint16_t size;3132/// \return33/// An \a llvm::Error if the record looks obviously wrong, or \a34/// llvm::Error::success() otherwise.35Error SanityCheck() const {36// The following checks are based on visual inspection of the records and37// enums in38// https://elixir.bootlin.com/linux/v4.8/source/include/uapi/linux/perf_event.h39// See PERF_RECORD_MAX, PERF_RECORD_SWITCH and the data similar records40// hold.4142// A record of too many uint64_t's or more should mean that the data is43// wrong44const uint64_t max_valid_size_bytes = 8000;45if (size == 0 || size > max_valid_size_bytes)46return createStringError(47inconvertibleErrorCode(),48formatv("A record of {0} bytes was found.", size));4950// We add some numbers to PERF_RECORD_MAX because some systems might have51// custom records. In any case, we are looking only for abnormal data.52if (type >= PERF_RECORD_MAX + 100)53return createStringError(54inconvertibleErrorCode(),55formatv("Invalid record type {0} was found.", type));56return Error::success();57}5859bool IsContextSwitchRecord() const {60return type == PERF_RECORD_SWITCH_CPU_WIDE;61}6263bool IsErrorRecord() const {64return type == PERF_RECORD_LOST || type == PERF_RECORD_THROTTLE ||65type == PERF_RECORD_UNTHROTTLE || type == PERF_RECORD_LOST_SAMPLES;66}67};68/// \}6970/// Record found in the perf_event context switch traces. It might contain71/// additional fields in memory, but header.size should have the actual size72/// of the record.73struct PerfContextSwitchRecord {74struct perf_event_header header;75uint32_t next_prev_pid;76uint32_t next_prev_tid;77uint32_t pid, tid;78uint64_t time_in_nanos;7980bool IsOut() const { return header.misc & PERF_RECORD_MISC_SWITCH_OUT; }81};8283/// Record produced after parsing the raw context switch trace produce by84/// perf_event. A major difference between this struct and85/// PerfContextSwitchRecord is that this one uses tsc instead of nanos.86struct ContextSwitchRecord {87uint64_t tsc;88/// Whether the switch is in or out89bool is_out;90/// pid = 0 and tid = 0 indicate the swapper or idle process, which normally91/// runs after a context switch out of a normal user thread.92lldb::pid_t pid;93lldb::tid_t tid;9495bool IsOut() const { return is_out; }9697bool IsIn() const { return !is_out; }98};99100uint64_t ThreadContinuousExecution::GetLowestKnownTSC() const {101switch (variant) {102case Variant::Complete:103return tscs.complete.start;104case Variant::OnlyStart:105return tscs.only_start.start;106case Variant::OnlyEnd:107return tscs.only_end.end;108case Variant::HintedEnd:109return tscs.hinted_end.start;110case Variant::HintedStart:111return tscs.hinted_start.end;112}113}114115uint64_t ThreadContinuousExecution::GetStartTSC() const {116switch (variant) {117case Variant::Complete:118return tscs.complete.start;119case Variant::OnlyStart:120return tscs.only_start.start;121case Variant::OnlyEnd:122return 0;123case Variant::HintedEnd:124return tscs.hinted_end.start;125case Variant::HintedStart:126return tscs.hinted_start.hinted_start;127}128}129130uint64_t ThreadContinuousExecution::GetEndTSC() const {131switch (variant) {132case Variant::Complete:133return tscs.complete.end;134case Variant::OnlyStart:135return std::numeric_limits<uint64_t>::max();136case Variant::OnlyEnd:137return tscs.only_end.end;138case Variant::HintedEnd:139return tscs.hinted_end.hinted_end;140case Variant::HintedStart:141return tscs.hinted_start.end;142}143}144145ThreadContinuousExecution ThreadContinuousExecution::CreateCompleteExecution(146lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,147uint64_t end) {148ThreadContinuousExecution o(cpu_id, tid, pid);149o.variant = Variant::Complete;150o.tscs.complete.start = start;151o.tscs.complete.end = end;152return o;153}154155ThreadContinuousExecution ThreadContinuousExecution::CreateHintedStartExecution(156lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid,157uint64_t hinted_start, uint64_t end) {158ThreadContinuousExecution o(cpu_id, tid, pid);159o.variant = Variant::HintedStart;160o.tscs.hinted_start.hinted_start = hinted_start;161o.tscs.hinted_start.end = end;162return o;163}164165ThreadContinuousExecution ThreadContinuousExecution::CreateHintedEndExecution(166lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,167uint64_t hinted_end) {168ThreadContinuousExecution o(cpu_id, tid, pid);169o.variant = Variant::HintedEnd;170o.tscs.hinted_end.start = start;171o.tscs.hinted_end.hinted_end = hinted_end;172return o;173}174175ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyEndExecution(176lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end) {177ThreadContinuousExecution o(cpu_id, tid, pid);178o.variant = Variant::OnlyEnd;179o.tscs.only_end.end = end;180return o;181}182183ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyStartExecution(184lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start) {185ThreadContinuousExecution o(cpu_id, tid, pid);186o.variant = Variant::OnlyStart;187o.tscs.only_start.start = start;188return o;189}190191static Error RecoverExecutionsFromConsecutiveRecords(192cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion,193const ContextSwitchRecord ¤t_record,194const std::optional<ContextSwitchRecord> &prev_record,195std::function<void(const ThreadContinuousExecution &execution)>196on_new_execution) {197if (!prev_record) {198if (current_record.IsOut()) {199on_new_execution(ThreadContinuousExecution::CreateOnlyEndExecution(200cpu_id, current_record.tid, current_record.pid, current_record.tsc));201}202// The 'in' case will be handled later when we try to look for its end203return Error::success();204}205206const ContextSwitchRecord &prev = *prev_record;207if (prev.tsc >= current_record.tsc)208return createStringError(209inconvertibleErrorCode(),210formatv("A context switch record doesn't happen after the previous "211"record. Previous TSC= {0}, current TSC = {1}.",212prev.tsc, current_record.tsc));213214if (current_record.IsIn() && prev.IsIn()) {215// We found two consecutive ins, which means that we didn't capture216// the end of the previous execution.217on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution(218cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));219} else if (current_record.IsOut() && prev.IsOut()) {220// We found two consecutive outs, that means that we didn't capture221// the beginning of the current execution.222on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution(223cpu_id, current_record.tid, current_record.pid, prev.tsc + 1,224current_record.tsc));225} else if (current_record.IsOut() && prev.IsIn()) {226if (current_record.pid == prev.pid && current_record.tid == prev.tid) {227/// A complete execution228on_new_execution(ThreadContinuousExecution::CreateCompleteExecution(229cpu_id, current_record.tid, current_record.pid, prev.tsc,230current_record.tsc));231} else {232// An out after the in of a different thread. The first one doesn't233// have an end, and the second one doesn't have a start.234on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution(235cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));236on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution(237cpu_id, current_record.tid, current_record.pid, prev.tsc + 1,238current_record.tsc));239}240}241return Error::success();242}243244Expected<std::vector<ThreadContinuousExecution>>245lldb_private::trace_intel_pt::DecodePerfContextSwitchTrace(246ArrayRef<uint8_t> data, cpu_id_t cpu_id,247const LinuxPerfZeroTscConversion &tsc_conversion) {248249std::vector<ThreadContinuousExecution> executions;250251// This offset is used to create the error message in case of failures.252size_t offset = 0;253254auto do_decode = [&]() -> Error {255std::optional<ContextSwitchRecord> prev_record;256while (offset < data.size()) {257const perf_event_header &perf_record =258*reinterpret_cast<const perf_event_header *>(data.data() + offset);259if (Error err = perf_record.SanityCheck())260return err;261262if (perf_record.IsContextSwitchRecord()) {263const PerfContextSwitchRecord &context_switch_record =264*reinterpret_cast<const PerfContextSwitchRecord *>(data.data() +265offset);266ContextSwitchRecord record{267tsc_conversion.ToTSC(context_switch_record.time_in_nanos),268context_switch_record.IsOut(),269static_cast<lldb::pid_t>(context_switch_record.pid),270static_cast<lldb::tid_t>(context_switch_record.tid)};271272if (Error err = RecoverExecutionsFromConsecutiveRecords(273cpu_id, tsc_conversion, record, prev_record,274[&](const ThreadContinuousExecution &execution) {275executions.push_back(execution);276}))277return err;278279prev_record = record;280}281offset += perf_record.size;282}283284// We might have an incomplete last record285if (prev_record && prev_record->IsIn())286executions.push_back(ThreadContinuousExecution::CreateOnlyStartExecution(287cpu_id, prev_record->tid, prev_record->pid, prev_record->tsc));288return Error::success();289};290291if (Error err = do_decode())292return createStringError(inconvertibleErrorCode(),293formatv("Malformed perf context switch trace for "294"cpu {0} at offset {1}. {2}",295cpu_id, offset, toString(std::move(err))));296297return executions;298}299300Expected<std::vector<uint8_t>>301lldb_private::trace_intel_pt::FilterProcessesFromContextSwitchTrace(302llvm::ArrayRef<uint8_t> data, const std::set<lldb::pid_t> &pids) {303size_t offset = 0;304std::vector<uint8_t> out_data;305306while (offset < data.size()) {307const perf_event_header &perf_record =308*reinterpret_cast<const perf_event_header *>(data.data() + offset);309if (Error err = perf_record.SanityCheck())310return std::move(err);311bool should_copy = false;312if (perf_record.IsContextSwitchRecord()) {313const PerfContextSwitchRecord &context_switch_record =314*reinterpret_cast<const PerfContextSwitchRecord *>(data.data() +315offset);316if (pids.count(context_switch_record.pid))317should_copy = true;318} else if (perf_record.IsErrorRecord()) {319should_copy = true;320}321322if (should_copy) {323for (size_t i = 0; i < perf_record.size; i++) {324out_data.push_back(data[offset + i]);325}326}327328offset += perf_record.size;329}330return out_data;331}332333334