1 //===-- DecodedThread.cpp -------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "DecodedThread.h"
10
11 #include <intel-pt.h>
12
13 #include "TraceCursorIntelPT.h"
14
15 #include <memory>
16
17 using namespace lldb;
18 using namespace lldb_private;
19 using namespace lldb_private::trace_intel_pt;
20 using namespace llvm;
21
IsLibiptError(int libipt_status)22 bool lldb_private::trace_intel_pt::IsLibiptError(int libipt_status) {
23 return libipt_status < 0;
24 }
25
IsEndOfStream(int libipt_status)26 bool lldb_private::trace_intel_pt::IsEndOfStream(int libipt_status) {
27 return libipt_status == -pte_eos;
28 }
29
IsTscUnavailable(int libipt_status)30 bool lldb_private::trace_intel_pt::IsTscUnavailable(int libipt_status) {
31 return libipt_status == -pte_no_time;
32 }
33
34 char IntelPTError::ID;
35
IntelPTError(int libipt_error_code,lldb::addr_t address)36 IntelPTError::IntelPTError(int libipt_error_code, lldb::addr_t address)
37 : m_libipt_error_code(libipt_error_code), m_address(address) {
38 assert(libipt_error_code < 0);
39 }
40
log(llvm::raw_ostream & OS) const41 void IntelPTError::log(llvm::raw_ostream &OS) const {
42 OS << pt_errstr(pt_errcode(m_libipt_error_code));
43 if (m_address != LLDB_INVALID_ADDRESS && m_address > 0)
44 OS << formatv(": {0:x+16}", m_address);
45 }
46
InRange(uint64_t item_index) const47 bool DecodedThread::TSCRange::InRange(uint64_t item_index) const {
48 return item_index >= first_item_index &&
49 item_index < first_item_index + items_count;
50 }
51
InRange(uint64_t item_index) const52 bool DecodedThread::NanosecondsRange::InRange(uint64_t item_index) const {
53 return item_index >= first_item_index &&
54 item_index < first_item_index + items_count;
55 }
56
GetInterpolatedTime(uint64_t item_index,uint64_t begin_of_time_nanos,const LinuxPerfZeroTscConversion & tsc_conversion) const57 double DecodedThread::NanosecondsRange::GetInterpolatedTime(
58 uint64_t item_index, uint64_t begin_of_time_nanos,
59 const LinuxPerfZeroTscConversion &tsc_conversion) const {
60 uint64_t items_since_last_tsc = item_index - first_item_index;
61
62 auto interpolate = [&](uint64_t next_range_start_ns) {
63 if (next_range_start_ns == nanos) {
64 // If the resolution of the conversion formula is bad enough to consider
65 // these two timestamps as equal, then we just increase the next one by 1
66 // for correction
67 next_range_start_ns++;
68 }
69 long double item_duration =
70 static_cast<long double>(items_count) / (next_range_start_ns - nanos);
71 return (nanos - begin_of_time_nanos) + items_since_last_tsc * item_duration;
72 };
73
74 if (!next_range) {
75 // If this is the last TSC range, so we have to extrapolate. In this case,
76 // we assume that each instruction took one TSC, which is what an
77 // instruction would take if no parallelism is achieved and the frequency
78 // multiplier is 1.
79 return interpolate(tsc_conversion.ToNanos(tsc + items_count));
80 }
81 if (items_count < (next_range->tsc - tsc)) {
82 // If the numbers of items in this range is less than the total TSC duration
83 // of this range, i.e. each instruction taking longer than 1 TSC, then we
84 // can assume that something else happened between these TSCs (e.g. a
85 // context switch, change to kernel, decoding errors, etc). In this case, we
86 // also assume that each instruction took 1 TSC. A proper way to improve
87 // this would be to analize the next events in the trace looking for context
88 // switches or trace disablement events, but for now, as we only want an
89 // approximation, we keep it simple. We are also guaranteed that the time in
90 // nanos of the next range is different to the current one, just because of
91 // the definition of a NanosecondsRange.
92 return interpolate(
93 std::min(tsc_conversion.ToNanos(tsc + items_count), next_range->nanos));
94 }
95
96 // In this case, each item took less than 1 TSC, so some parallelism was
97 // achieved, which is an indication that we didn't suffered of any kind of
98 // interruption.
99 return interpolate(next_range->nanos);
100 }
101
GetItemsCount() const102 uint64_t DecodedThread::GetItemsCount() const { return m_item_kinds.size(); }
103
104 lldb::addr_t
GetInstructionLoadAddress(uint64_t item_index) const105 DecodedThread::GetInstructionLoadAddress(uint64_t item_index) const {
106 return m_item_data[item_index].load_address;
107 }
108
GetThread()109 ThreadSP DecodedThread::GetThread() { return m_thread_sp; }
110
111 DecodedThread::TraceItemStorage &
CreateNewTraceItem(lldb::TraceItemKind kind)112 DecodedThread::CreateNewTraceItem(lldb::TraceItemKind kind) {
113 m_item_kinds.push_back(kind);
114 m_item_data.emplace_back();
115 if (m_last_tsc)
116 (*m_last_tsc)->second.items_count++;
117 if (m_last_nanoseconds)
118 (*m_last_nanoseconds)->second.items_count++;
119 return m_item_data.back();
120 }
121
NotifyTsc(TSC tsc)122 void DecodedThread::NotifyTsc(TSC tsc) {
123 if (m_last_tsc && (*m_last_tsc)->second.tsc == tsc)
124 return;
125
126 m_last_tsc =
127 m_tscs.emplace(GetItemsCount(), TSCRange{tsc, 0, GetItemsCount()}).first;
128
129 if (m_tsc_conversion) {
130 uint64_t nanos = m_tsc_conversion->ToNanos(tsc);
131 if (!m_last_nanoseconds || (*m_last_nanoseconds)->second.nanos != nanos) {
132 m_last_nanoseconds =
133 m_nanoseconds
134 .emplace(GetItemsCount(), NanosecondsRange{nanos, tsc, nullptr, 0,
135 GetItemsCount()})
136 .first;
137 if (*m_last_nanoseconds != m_nanoseconds.begin()) {
138 auto prev_range = prev(*m_last_nanoseconds);
139 prev_range->second.next_range = &(*m_last_nanoseconds)->second;
140 }
141 }
142 }
143 AppendEvent(lldb::eTraceEventHWClockTick);
144 }
145
NotifyCPU(lldb::cpu_id_t cpu_id)146 void DecodedThread::NotifyCPU(lldb::cpu_id_t cpu_id) {
147 if (!m_last_cpu || *m_last_cpu != cpu_id) {
148 m_cpus.emplace(GetItemsCount(), cpu_id);
149 m_last_cpu = cpu_id;
150 AppendEvent(lldb::eTraceEventCPUChanged);
151 }
152 }
153
154 Optional<lldb::cpu_id_t>
GetCPUByIndex(uint64_t item_index) const155 DecodedThread::GetCPUByIndex(uint64_t item_index) const {
156 auto it = m_cpus.upper_bound(item_index);
157 if (it == m_cpus.begin())
158 return None;
159 return prev(it)->second;
160 }
161
162 Optional<DecodedThread::TSCRange>
GetTSCRangeByIndex(uint64_t item_index) const163 DecodedThread::GetTSCRangeByIndex(uint64_t item_index) const {
164 auto next_it = m_tscs.upper_bound(item_index);
165 if (next_it == m_tscs.begin())
166 return None;
167 return prev(next_it)->second;
168 }
169
170 Optional<DecodedThread::NanosecondsRange>
GetNanosecondsRangeByIndex(uint64_t item_index)171 DecodedThread::GetNanosecondsRangeByIndex(uint64_t item_index) {
172 auto next_it = m_nanoseconds.upper_bound(item_index);
173 if (next_it == m_nanoseconds.begin())
174 return None;
175 return prev(next_it)->second;
176 }
177
AppendEvent(lldb::TraceEvent event)178 void DecodedThread::AppendEvent(lldb::TraceEvent event) {
179 CreateNewTraceItem(lldb::eTraceItemKindEvent).event = event;
180 m_events_stats.RecordEvent(event);
181 }
182
AppendInstruction(const pt_insn & insn)183 void DecodedThread::AppendInstruction(const pt_insn &insn) {
184 CreateNewTraceItem(lldb::eTraceItemKindInstruction).load_address = insn.ip;
185 }
186
AppendError(const IntelPTError & error)187 void DecodedThread::AppendError(const IntelPTError &error) {
188 // End of stream shouldn't be a public error
189 if (IsEndOfStream(error.GetLibiptErrorCode()))
190 return;
191 CreateNewTraceItem(lldb::eTraceItemKindError).error =
192 ConstString(error.message()).AsCString();
193 }
194
AppendCustomError(StringRef err)195 void DecodedThread::AppendCustomError(StringRef err) {
196 CreateNewTraceItem(lldb::eTraceItemKindError).error =
197 ConstString(err).AsCString();
198 }
199
GetEventByIndex(int item_index) const200 lldb::TraceEvent DecodedThread::GetEventByIndex(int item_index) const {
201 return m_item_data[item_index].event;
202 }
203
RecordError(int libipt_error_code)204 void DecodedThread::LibiptErrorsStats::RecordError(int libipt_error_code) {
205 libipt_errors_counts[pt_errstr(pt_errcode(libipt_error_code))]++;
206 total_count++;
207 }
208
RecordTscError(int libipt_error_code)209 void DecodedThread::RecordTscError(int libipt_error_code) {
210 m_tsc_errors_stats.RecordError(libipt_error_code);
211 }
212
213 const DecodedThread::LibiptErrorsStats &
GetTscErrorsStats() const214 DecodedThread::GetTscErrorsStats() const {
215 return m_tsc_errors_stats;
216 }
217
GetEventsStats() const218 const DecodedThread::EventsStats &DecodedThread::GetEventsStats() const {
219 return m_events_stats;
220 }
221
RecordEvent(lldb::TraceEvent event)222 void DecodedThread::EventsStats::RecordEvent(lldb::TraceEvent event) {
223 events_counts[event]++;
224 total_count++;
225 }
226
227 lldb::TraceItemKind
GetItemKindByIndex(uint64_t item_index) const228 DecodedThread::GetItemKindByIndex(uint64_t item_index) const {
229 return static_cast<lldb::TraceItemKind>(m_item_kinds[item_index]);
230 }
231
GetErrorByIndex(uint64_t item_index) const232 const char *DecodedThread::GetErrorByIndex(uint64_t item_index) const {
233 return m_item_data[item_index].error;
234 }
235
DecodedThread(ThreadSP thread_sp,const llvm::Optional<LinuxPerfZeroTscConversion> & tsc_conversion)236 DecodedThread::DecodedThread(
237 ThreadSP thread_sp,
238 const llvm::Optional<LinuxPerfZeroTscConversion> &tsc_conversion)
239 : m_thread_sp(thread_sp), m_tsc_conversion(tsc_conversion) {}
240
CalculateApproximateMemoryUsage() const241 size_t DecodedThread::CalculateApproximateMemoryUsage() const {
242 return sizeof(TraceItemStorage) * m_item_data.size() +
243 sizeof(uint8_t) * m_item_kinds.size() +
244 (sizeof(uint64_t) + sizeof(TSC)) * m_tscs.size() +
245 (sizeof(uint64_t) + sizeof(uint64_t)) * m_nanoseconds.size() +
246 (sizeof(uint64_t) + sizeof(lldb::cpu_id_t)) * m_cpus.size();
247 }
248