LLDB mainline
PerfContextSwitchDecoder.cpp
Go to the documentation of this file.
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
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 on
17/// non-linux platforms.
18/// \{
19#define PERF_RECORD_MISC_SWITCH_OUT (1 << 13)
20
21#define PERF_RECORD_LOST 2
22#define PERF_RECORD_THROTTLE 5
23#define PERF_RECORD_UNTHROTTLE 6
24#define PERF_RECORD_LOST_SAMPLES 13
25#define PERF_RECORD_SWITCH_CPU_WIDE 15
26#define PERF_RECORD_MAX 19
27
29 uint32_t type;
30 uint16_t misc;
31 uint16_t size;
32
33 /// \return
34 /// An \a llvm::Error if the record looks obviously wrong, or \a
35 /// llvm::Error::success() otherwise.
37 // The following checks are based on visual inspection of the records and
38 // enums in
39 // https://elixir.bootlin.com/linux/v4.8/source/include/uapi/linux/perf_event.h
40 // See PERF_RECORD_MAX, PERF_RECORD_SWITCH and the data similar records
41 // hold.
42
43 // A record of too many uint64_t's or more should mean that the data is
44 // wrong
45 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 have
52 // 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 {
62 }
63
64 bool IsErrorRecord() const {
67 }
68};
69/// \}
70
71/// Record found in the perf_event context switch traces. It might contain
72/// additional fields in memory, but header.size should have the actual size
73/// of the record.
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 by
85/// perf_event. A major difference between this struct and
86/// PerfContextSwitchRecord is that this one uses tsc instead of nanos.
88 uint64_t tsc;
89 /// Whether the switch is in or out
90 bool is_out;
91 /// pid = 0 and tid = 0 indicate the swapper or idle process, which normally
92 /// runs after a context switch out of a normal user thread.
95
96 bool IsOut() const { return is_out; }
97
98 bool IsIn() const { return !is_out; }
99};
100
102 switch (variant) {
104 return tscs.complete.start;
106 return tscs.only_start.start;
107 case Variant::OnlyEnd:
108 return tscs.only_end.end;
110 return tscs.hinted_end.start;
112 return tscs.hinted_start.end;
113 }
114}
115
117 switch (variant) {
119 return tscs.complete.start;
121 return tscs.only_start.start;
122 case Variant::OnlyEnd:
123 return 0;
125 return tscs.hinted_end.start;
127 return tscs.hinted_start.hinted_start;
128 }
129}
130
132 switch (variant) {
134 return tscs.complete.end;
136 return std::numeric_limits<uint64_t>::max();
137 case Variant::OnlyEnd:
138 return tscs.only_end.end;
140 return tscs.hinted_end.hinted_end;
142 return tscs.hinted_start.end;
143 }
144}
145
147 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,
148 uint64_t end) {
152 o.tscs.complete.end = end;
153 return o;
154}
155
157 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid,
158 uint64_t hinted_start, uint64_t end) {
161 o.tscs.hinted_start.hinted_start = hinted_start;
162 o.tscs.hinted_start.end = end;
163 return o;
164}
165
167 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,
168 uint64_t hinted_end) {
171 o.tscs.hinted_end.start = start;
172 o.tscs.hinted_end.hinted_end = hinted_end;
173 return o;
174}
175
177 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end) {
180 o.tscs.only_end.end = end;
181 return o;
182}
183
185 lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start) {
189 return o;
190}
191
193 cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion,
194 const ContextSwitchRecord &current_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()) {
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 end
204 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 capture
217 // the end of the previous execution.
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 capture
222 // the beginning of the current execution.
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 execution
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't
234 // have an end, and the second one doesn't have a start.
236 cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));
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>>
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
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 record
286 if (prev_record && prev_record->IsIn())
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>>
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}
#define PERF_RECORD_MISC_SWITCH_OUT
Copied from <linux/perf_event.h> to avoid depending on perf_event.h on non-linux platforms.
#define PERF_RECORD_UNTHROTTLE
#define PERF_RECORD_LOST_SAMPLES
static Error RecoverExecutionsFromConsecutiveRecords(cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion, const ContextSwitchRecord &current_record, const std::optional< ContextSwitchRecord > &prev_record, std::function< void(const ThreadContinuousExecution &execution)> on_new_execution)
#define PERF_RECORD_LOST
#define PERF_RECORD_MAX
#define PERF_RECORD_THROTTLE
#define PERF_RECORD_SWITCH_CPU_WIDE
llvm::Error Error
llvm::Expected< std::vector< uint8_t > > FilterProcessesFromContextSwitchTrace(llvm::ArrayRef< uint8_t > data, const std::set< lldb::pid_t > &pids)
llvm::Expected< std::vector< ThreadContinuousExecution > > DecodePerfContextSwitchTrace(llvm::ArrayRef< uint8_t > data, lldb::cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion)
Decodes a context switch trace collected with perf_event_open.
A class that represents a running process on the host machine.
Definition: SBAttachInfo.h:14
const char * toString(AppleArm64ExceptionClass EC)
Definition: SBAddress.h:15
uint64_t pid_t
Definition: lldb-types.h:81
uint32_t cpu_id_t
Definition: lldb-types.h:89
uint64_t tid_t
Definition: lldb-types.h:82
Definition: Debugger.h:53
Record produced after parsing the raw context switch trace produce by perf_event.
bool is_out
Whether the switch is in or out.
lldb::pid_t pid
pid = 0 and tid = 0 indicate the swapper or idle process, which normally runs after a context switch ...
Record found in the perf_event context switch traces.
struct perf_event_header header
This class indicates the time interval in which a thread was running continuously on a cpu core.
struct lldb_private::trace_intel_pt::ThreadContinuousExecution::@166::@167 complete
struct lldb_private::trace_intel_pt::ThreadContinuousExecution::@166::@169 only_end
static ThreadContinuousExecution CreateHintedEndExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start, uint64_t hinted_end)
static ThreadContinuousExecution CreateOnlyStartExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start)
enum lldb_private::trace_intel_pt::ThreadContinuousExecution::Variant variant
@ OnlyEnd
We only know the end. This might be the first entry or a cpu trace.
@ HintedEnd
The start is known and we have an upper bound for the end, i.e.
@ HintedStart
The end is known and we have a lower bound for the start, i.e.
@ OnlyStart
We only know the start. This might be the last entry of a cpu trace.
static ThreadContinuousExecution CreateCompleteExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start, uint64_t end)
Constructors for the different variants of this object.
static ThreadContinuousExecution CreateHintedStartExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t hinted_start, uint64_t end)
struct lldb_private::trace_intel_pt::ThreadContinuousExecution::@166::@168 only_start
union lldb_private::trace_intel_pt::ThreadContinuousExecution::@166 tscs
static ThreadContinuousExecution CreateOnlyEndExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end)