xref: /llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.cpp (revision a19fcc2bec81989f90700cfc63b89a0dfd330197)
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 &current_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