333 lines · cpp
1//===-- PerfContextSwitchDecoder.cpp --======------------------------------===//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7 8#include "PerfContextSwitchDecoder.h"9#include <optional>10 11using namespace lldb;12using namespace lldb_private;13using namespace lldb_private::trace_intel_pt;14using namespace llvm;15 16/// Copied from <linux/perf_event.h> to avoid depending on perf_event.h on17/// non-linux platforms.18/// \{19#define PERF_RECORD_MISC_SWITCH_OUT (1 << 13)20 21#define PERF_RECORD_LOST 222#define PERF_RECORD_THROTTLE 523#define PERF_RECORD_UNTHROTTLE 624#define PERF_RECORD_LOST_SAMPLES 1325#define PERF_RECORD_SWITCH_CPU_WIDE 1526#define PERF_RECORD_MAX 1927 28struct perf_event_header {29 uint32_t type;30 uint16_t misc;31 uint16_t size;32 33 /// \return34 /// An \a llvm::Error if the record looks obviously wrong, or \a35 /// llvm::Error::success() otherwise.36 Error SanityCheck() const {37 // The following checks are based on visual inspection of the records and38 // enums in39 // https://elixir.bootlin.com/linux/v4.8/source/include/uapi/linux/perf_event.h40 // See PERF_RECORD_MAX, PERF_RECORD_SWITCH and the data similar records41 // hold.42 43 // A record of too many uint64_t's or more should mean that the data is44 // wrong45 const uint64_t max_valid_size_bytes = 8000;46 if (size == 0 || size > max_valid_size_bytes)47 return createStringError(48 inconvertibleErrorCode(),49 formatv("A record of {0} bytes was found.", size));50 51 // We add some numbers to PERF_RECORD_MAX because some systems might have52 // custom records. In any case, we are looking only for abnormal data.53 if (type >= PERF_RECORD_MAX + 100)54 return createStringError(55 inconvertibleErrorCode(),56 formatv("Invalid record type {0} was found.", type));57 return Error::success();58 }59 60 bool IsContextSwitchRecord() const {61 return type == PERF_RECORD_SWITCH_CPU_WIDE;62 }63 64 bool IsErrorRecord() const {65 return type == PERF_RECORD_LOST || type == PERF_RECORD_THROTTLE ||66 type == PERF_RECORD_UNTHROTTLE || type == PERF_RECORD_LOST_SAMPLES;67 }68};69/// \}70 71/// Record found in the perf_event context switch traces. It might contain72/// additional fields in memory, but header.size should have the actual size73/// of the record.74struct PerfContextSwitchRecord {75 struct perf_event_header header;76 uint32_t next_prev_pid;77 uint32_t next_prev_tid;78 uint32_t pid, tid;79 uint64_t time_in_nanos;80 81 bool IsOut() const { return header.misc & PERF_RECORD_MISC_SWITCH_OUT; }82};83 84/// Record produced after parsing the raw context switch trace produce by85/// perf_event. A major difference between this struct and86/// PerfContextSwitchRecord is that this one uses tsc instead of nanos.87struct ContextSwitchRecord {88 uint64_t tsc;89 /// Whether the switch is in or out90 bool is_out;91 /// pid = 0 and tid = 0 indicate the swapper or idle process, which normally92 /// runs after a context switch out of a normal user thread.93 lldb::pid_t pid;94 lldb::tid_t tid;95 96 bool IsOut() const { return is_out; }97 98 bool IsIn() const { return !is_out; }99};100 101uint64_t ThreadContinuousExecution::GetLowestKnownTSC() const {102 switch (variant) {103 case Variant::Complete:104 return tscs.complete.start;105 case Variant::OnlyStart:106 return tscs.only_start.start;107 case Variant::OnlyEnd:108 return tscs.only_end.end;109 case Variant::HintedEnd:110 return tscs.hinted_end.start;111 case Variant::HintedStart:112 return tscs.hinted_start.end;113 }114}115 116uint64_t ThreadContinuousExecution::GetStartTSC() const {117 switch (variant) {118 case Variant::Complete:119 return tscs.complete.start;120 case Variant::OnlyStart:121 return tscs.only_start.start;122 case Variant::OnlyEnd:123 return 0;124 case Variant::HintedEnd:125 return tscs.hinted_end.start;126 case Variant::HintedStart:127 return tscs.hinted_start.hinted_start;128 }129}130 131uint64_t ThreadContinuousExecution::GetEndTSC() const {132 switch (variant) {133 case Variant::Complete:134 return tscs.complete.end;135 case Variant::OnlyStart:136 return std::numeric_limits<uint64_t>::max();137 case Variant::OnlyEnd:138 return tscs.only_end.end;139 case Variant::HintedEnd:140 return tscs.hinted_end.hinted_end;141 case Variant::HintedStart:142 return tscs.hinted_start.end;143 }144}145 146ThreadContinuousExecution ThreadContinuousExecution::CreateCompleteExecution(147 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,148 uint64_t end) {149 ThreadContinuousExecution o(cpu_id, tid, pid);150 o.variant = Variant::Complete;151 o.tscs.complete.start = start;152 o.tscs.complete.end = end;153 return o;154}155 156ThreadContinuousExecution ThreadContinuousExecution::CreateHintedStartExecution(157 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid,158 uint64_t hinted_start, uint64_t end) {159 ThreadContinuousExecution o(cpu_id, tid, pid);160 o.variant = Variant::HintedStart;161 o.tscs.hinted_start.hinted_start = hinted_start;162 o.tscs.hinted_start.end = end;163 return o;164}165 166ThreadContinuousExecution ThreadContinuousExecution::CreateHintedEndExecution(167 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,168 uint64_t hinted_end) {169 ThreadContinuousExecution o(cpu_id, tid, pid);170 o.variant = Variant::HintedEnd;171 o.tscs.hinted_end.start = start;172 o.tscs.hinted_end.hinted_end = hinted_end;173 return o;174}175 176ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyEndExecution(177 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end) {178 ThreadContinuousExecution o(cpu_id, tid, pid);179 o.variant = Variant::OnlyEnd;180 o.tscs.only_end.end = end;181 return o;182}183 184ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyStartExecution(185 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start) {186 ThreadContinuousExecution o(cpu_id, tid, pid);187 o.variant = Variant::OnlyStart;188 o.tscs.only_start.start = start;189 return o;190}191 192static Error RecoverExecutionsFromConsecutiveRecords(193 cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion,194 const ContextSwitchRecord ¤t_record,195 const std::optional<ContextSwitchRecord> &prev_record,196 std::function<void(const ThreadContinuousExecution &execution)>197 on_new_execution) {198 if (!prev_record) {199 if (current_record.IsOut()) {200 on_new_execution(ThreadContinuousExecution::CreateOnlyEndExecution(201 cpu_id, current_record.tid, current_record.pid, current_record.tsc));202 }203 // The 'in' case will be handled later when we try to look for its end204 return Error::success();205 }206 207 const ContextSwitchRecord &prev = *prev_record;208 if (prev.tsc >= current_record.tsc)209 return createStringError(210 inconvertibleErrorCode(),211 formatv("A context switch record doesn't happen after the previous "212 "record. Previous TSC= {0}, current TSC = {1}.",213 prev.tsc, current_record.tsc));214 215 if (current_record.IsIn() && prev.IsIn()) {216 // We found two consecutive ins, which means that we didn't capture217 // the end of the previous execution.218 on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution(219 cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));220 } else if (current_record.IsOut() && prev.IsOut()) {221 // We found two consecutive outs, that means that we didn't capture222 // the beginning of the current execution.223 on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution(224 cpu_id, current_record.tid, current_record.pid, prev.tsc + 1,225 current_record.tsc));226 } else if (current_record.IsOut() && prev.IsIn()) {227 if (current_record.pid == prev.pid && current_record.tid == prev.tid) {228 /// A complete execution229 on_new_execution(ThreadContinuousExecution::CreateCompleteExecution(230 cpu_id, current_record.tid, current_record.pid, prev.tsc,231 current_record.tsc));232 } else {233 // An out after the in of a different thread. The first one doesn't234 // have an end, and the second one doesn't have a start.235 on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution(236 cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));237 on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution(238 cpu_id, current_record.tid, current_record.pid, prev.tsc + 1,239 current_record.tsc));240 }241 }242 return Error::success();243}244 245Expected<std::vector<ThreadContinuousExecution>>246lldb_private::trace_intel_pt::DecodePerfContextSwitchTrace(247 ArrayRef<uint8_t> data, cpu_id_t cpu_id,248 const LinuxPerfZeroTscConversion &tsc_conversion) {249 250 std::vector<ThreadContinuousExecution> executions;251 252 // This offset is used to create the error message in case of failures.253 size_t offset = 0;254 255 auto do_decode = [&]() -> Error {256 std::optional<ContextSwitchRecord> prev_record;257 while (offset < data.size()) {258 const perf_event_header &perf_record =259 *reinterpret_cast<const perf_event_header *>(data.data() + offset);260 if (Error err = perf_record.SanityCheck())261 return err;262 263 if (perf_record.IsContextSwitchRecord()) {264 const PerfContextSwitchRecord &context_switch_record =265 *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() +266 offset);267 ContextSwitchRecord record{268 tsc_conversion.ToTSC(context_switch_record.time_in_nanos),269 context_switch_record.IsOut(),270 static_cast<lldb::pid_t>(context_switch_record.pid),271 static_cast<lldb::tid_t>(context_switch_record.tid)};272 273 if (Error err = RecoverExecutionsFromConsecutiveRecords(274 cpu_id, tsc_conversion, record, prev_record,275 [&](const ThreadContinuousExecution &execution) {276 executions.push_back(execution);277 }))278 return err;279 280 prev_record = record;281 }282 offset += perf_record.size;283 }284 285 // We might have an incomplete last record286 if (prev_record && prev_record->IsIn())287 executions.push_back(ThreadContinuousExecution::CreateOnlyStartExecution(288 cpu_id, prev_record->tid, prev_record->pid, prev_record->tsc));289 return Error::success();290 };291 292 if (Error err = do_decode())293 return createStringError(inconvertibleErrorCode(),294 formatv("Malformed perf context switch trace for "295 "cpu {0} at offset {1}. {2}",296 cpu_id, offset, toString(std::move(err))));297 298 return executions;299}300 301Expected<std::vector<uint8_t>>302lldb_private::trace_intel_pt::FilterProcessesFromContextSwitchTrace(303 llvm::ArrayRef<uint8_t> data, const std::set<lldb::pid_t> &pids) {304 size_t offset = 0;305 std::vector<uint8_t> out_data;306 307 while (offset < data.size()) {308 const perf_event_header &perf_record =309 *reinterpret_cast<const perf_event_header *>(data.data() + offset);310 if (Error err = perf_record.SanityCheck())311 return std::move(err);312 bool should_copy = false;313 if (perf_record.IsContextSwitchRecord()) {314 const PerfContextSwitchRecord &context_switch_record =315 *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() +316 offset);317 if (pids.count(context_switch_record.pid))318 should_copy = true;319 } else if (perf_record.IsErrorRecord()) {320 should_copy = true;321 }322 323 if (should_copy) {324 for (size_t i = 0; i < perf_record.size; i++) {325 out_data.push_back(data[offset + i]);326 }327 }328 329 offset += perf_record.size;330 }331 return out_data;332}333