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-exception 5 // 6 //===----------------------------------------------------------------------===// 7 8 #include "PerfContextSwitchDecoder.h" 9 10 using namespace lldb; 11 using namespace lldb_private; 12 using namespace lldb_private::trace_intel_pt; 13 using namespace llvm; 14 15 /// Copied from <linux/perf_event.h> to avoid depending on perf_event.h on 16 /// non-linux platforms. 17 /// \{ 18 struct perf_event_header { 19 uint32_t type; 20 uint16_t misc; 21 uint16_t size; 22 }; 23 24 #define PERF_RECORD_MISC_SWITCH_OUT (1 << 13) 25 #define PERF_RECORD_MAX 19 26 #define PERF_RECORD_SWITCH_CPU_WIDE 15 27 /// \} 28 29 /// Record found in the perf_event context switch traces. It might contain 30 /// additional fields in memory, but header.size should have the actual size 31 /// of the record. 32 struct PerfContextSwitchRecord { 33 struct perf_event_header header; 34 uint32_t next_prev_pid; 35 uint32_t next_prev_tid; 36 uint32_t pid, tid; 37 uint64_t time_in_nanos; 38 39 bool IsOut() const { return header.misc & PERF_RECORD_MISC_SWITCH_OUT; } 40 41 bool IsContextSwitchRecord() const { 42 return header.type == PERF_RECORD_SWITCH_CPU_WIDE; 43 } 44 45 /// \return 46 /// An \a llvm::Error if the record looks obviously wrong, or \a 47 /// llvm::Error::success() otherwise. 48 Error SanityCheck() const { 49 // A record of too many uint64_t's or more should mean that the data is 50 // wrong 51 if (header.size == 0 || header.size > sizeof(uint64_t) * 1000) 52 return createStringError( 53 inconvertibleErrorCode(), 54 formatv("A record of {0} bytes was found.", header.size)); 55 56 // We add some numbers to PERF_RECORD_MAX because some systems might have 57 // custom records. In any case, we are looking only for abnormal data. 58 if (header.type >= PERF_RECORD_MAX + 100) 59 return createStringError( 60 inconvertibleErrorCode(), 61 formatv("Invalid record type {0} was found.", header.type)); 62 return Error::success(); 63 } 64 }; 65 66 /// Record produced after parsing the raw context switch trace produce by 67 /// perf_event. A major difference between this struct and 68 /// PerfContextSwitchRecord is that this one uses tsc instead of nanos. 69 struct ContextSwitchRecord { 70 uint64_t tsc; 71 /// Whether the switch is in or out 72 bool is_out; 73 /// pid = 0 and tid = 0 indicate the swapper or idle process, which normally 74 /// runs after a context switch out of a normal user thread. 75 lldb::pid_t pid; 76 lldb::tid_t tid; 77 78 bool IsOut() const { return is_out; } 79 80 bool IsIn() const { return !is_out; } 81 }; 82 83 uint64_t ThreadContinuousExecution::GetLowestKnownTSC() const { 84 switch (variant) { 85 case Variant::Complete: 86 return tscs.complete.start; 87 case Variant::OnlyStart: 88 return tscs.only_start.start; 89 case Variant::OnlyEnd: 90 return tscs.only_end.end; 91 case Variant::HintedEnd: 92 return tscs.hinted_end.start; 93 case Variant::HintedStart: 94 return tscs.hinted_start.end; 95 } 96 } 97 98 uint64_t ThreadContinuousExecution::GetStartTSC() const { 99 switch (variant) { 100 case Variant::Complete: 101 return tscs.complete.start; 102 case Variant::OnlyStart: 103 return tscs.only_start.start; 104 case Variant::OnlyEnd: 105 return 0; 106 case Variant::HintedEnd: 107 return tscs.hinted_end.start; 108 case Variant::HintedStart: 109 return tscs.hinted_start.hinted_start; 110 } 111 } 112 113 uint64_t ThreadContinuousExecution::GetEndTSC() const { 114 switch (variant) { 115 case Variant::Complete: 116 return tscs.complete.end; 117 case Variant::OnlyStart: 118 return std::numeric_limits<uint64_t>::max(); 119 case Variant::OnlyEnd: 120 return tscs.only_end.end; 121 case Variant::HintedEnd: 122 return tscs.hinted_end.hinted_end; 123 case Variant::HintedStart: 124 return tscs.hinted_start.end; 125 } 126 } 127 128 ThreadContinuousExecution ThreadContinuousExecution::CreateCompleteExecution( 129 lldb::core_id_t core_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start, 130 uint64_t end) { 131 ThreadContinuousExecution o(core_id, tid, pid); 132 o.variant = Variant::Complete; 133 o.tscs.complete.start = start; 134 o.tscs.complete.end = end; 135 return o; 136 } 137 138 ThreadContinuousExecution ThreadContinuousExecution::CreateHintedStartExecution( 139 lldb::core_id_t core_id, lldb::tid_t tid, lldb::pid_t pid, 140 uint64_t hinted_start, uint64_t end) { 141 ThreadContinuousExecution o(core_id, tid, pid); 142 o.variant = Variant::HintedStart; 143 o.tscs.hinted_start.hinted_start = hinted_start; 144 o.tscs.hinted_start.end = end; 145 return o; 146 } 147 148 ThreadContinuousExecution ThreadContinuousExecution::CreateHintedEndExecution( 149 lldb::core_id_t core_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start, 150 uint64_t hinted_end) { 151 ThreadContinuousExecution o(core_id, tid, pid); 152 o.variant = Variant::HintedEnd; 153 o.tscs.hinted_end.start = start; 154 o.tscs.hinted_end.hinted_end = hinted_end; 155 return o; 156 } 157 158 ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyEndExecution( 159 lldb::core_id_t core_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end) { 160 ThreadContinuousExecution o(core_id, tid, pid); 161 o.variant = Variant::OnlyEnd; 162 o.tscs.only_end.end = end; 163 return o; 164 } 165 166 ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyStartExecution( 167 lldb::core_id_t core_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start) { 168 ThreadContinuousExecution o(core_id, tid, pid); 169 o.variant = Variant::OnlyStart; 170 o.tscs.only_start.start = start; 171 return o; 172 } 173 174 static Error RecoverExecutionsFromConsecutiveRecords( 175 core_id_t core_id, const LinuxPerfZeroTscConversion &tsc_conversion, 176 const ContextSwitchRecord ¤t_record, 177 const Optional<ContextSwitchRecord> &prev_record, 178 std::function<void(const ThreadContinuousExecution &execution)> 179 on_new_execution) { 180 if (!prev_record) { 181 if (current_record.IsOut()) { 182 on_new_execution(ThreadContinuousExecution::CreateOnlyEndExecution( 183 core_id, current_record.tid, current_record.pid, current_record.tsc)); 184 } 185 // The 'in' case will be handled later when we try to look for its end 186 return Error::success(); 187 } 188 189 const ContextSwitchRecord &prev = *prev_record; 190 if (prev.tsc >= current_record.tsc) 191 return createStringError( 192 inconvertibleErrorCode(), 193 formatv("A context switch record doesn't happen after the previous " 194 "record. Previous TSC= {0}, current TSC = {1}.", 195 prev.tsc, current_record.tsc)); 196 197 if (current_record.IsIn() && prev.IsIn()) { 198 // We found two consecutive ins, which means that we didn't capture 199 // the end of the previous execution. 200 on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution( 201 core_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1)); 202 } else if (current_record.IsOut() && prev.IsOut()) { 203 // We found two consecutive outs, that means that we didn't capture 204 // the beginning of the current execution. 205 on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution( 206 core_id, current_record.tid, current_record.pid, prev.tsc + 1, 207 current_record.tsc)); 208 } else if (current_record.IsOut() && prev.IsIn()) { 209 if (current_record.pid == prev.pid && current_record.tid == prev.tid) { 210 /// A complete execution 211 on_new_execution(ThreadContinuousExecution::CreateCompleteExecution( 212 core_id, current_record.tid, current_record.pid, prev.tsc, 213 current_record.tsc)); 214 } else { 215 // An out after the in of a different thread. The first one doesn't 216 // have an end, and the second one doesn't have a start. 217 on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution( 218 core_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1)); 219 on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution( 220 core_id, current_record.tid, current_record.pid, prev.tsc + 1, 221 current_record.tsc)); 222 } 223 } 224 return Error::success(); 225 } 226 227 #include <fstream> 228 229 Expected<std::vector<ThreadContinuousExecution>> 230 lldb_private::trace_intel_pt::DecodePerfContextSwitchTrace( 231 ArrayRef<uint8_t> data, core_id_t core_id, 232 const LinuxPerfZeroTscConversion &tsc_conversion) { 233 234 std::vector<ThreadContinuousExecution> executions; 235 236 // This offset is used to create the error message in case of failures. 237 size_t offset = 0; 238 239 auto do_decode = [&]() -> Error { 240 Optional<ContextSwitchRecord> prev_record; 241 while (offset < data.size()) { 242 const PerfContextSwitchRecord &perf_record = 243 *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() + 244 offset); 245 if (Error err = perf_record.SanityCheck()) 246 return err; 247 248 if (perf_record.IsContextSwitchRecord()) { 249 ContextSwitchRecord record{ 250 tsc_conversion.ToTSC(perf_record.time_in_nanos), 251 perf_record.IsOut(), static_cast<lldb::pid_t>(perf_record.pid), 252 static_cast<lldb::tid_t>(perf_record.tid)}; 253 254 if (Error err = RecoverExecutionsFromConsecutiveRecords( 255 core_id, tsc_conversion, record, prev_record, 256 [&](const ThreadContinuousExecution &execution) { 257 executions.push_back(execution); 258 })) 259 return err; 260 261 prev_record = record; 262 } 263 offset += perf_record.header.size; 264 } 265 266 // We might have an incomplete last record 267 if (prev_record && prev_record->IsIn()) 268 executions.push_back(ThreadContinuousExecution::CreateOnlyStartExecution( 269 core_id, prev_record->tid, prev_record->pid, prev_record->tsc)); 270 return Error::success(); 271 }; 272 273 if (Error err = do_decode()) 274 return createStringError(inconvertibleErrorCode(), 275 formatv("Malformed perf context switch trace for " 276 "cpu {0} at offset {1}. {2}", 277 core_id, offset, 278 toString(std::move(err)))); 279 280 return executions; 281 } 282