1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/importers/perf/record_parser.h"
18
19 #include <cinttypes>
20 #include <cstdint>
21 #include <optional>
22 #include <string>
23 #include <utility>
24 #include <vector>
25
26 #include "perfetto/base/logging.h"
27 #include "perfetto/base/status.h"
28 #include "perfetto/ext/base/string_view.h"
29 #include "perfetto/public/compiler.h"
30 #include "perfetto/trace_processor/ref_counted.h"
31 #include "src/trace_processor/importers/common/address_range.h"
32 #include "src/trace_processor/importers/common/create_mapping_params.h"
33 #include "src/trace_processor/importers/common/mapping_tracker.h"
34 #include "src/trace_processor/importers/common/process_tracker.h"
35 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
36 #include "src/trace_processor/importers/common/virtual_memory_mapping.h"
37 #include "src/trace_processor/importers/perf/itrace_start_record.h"
38 #include "src/trace_processor/importers/perf/mmap_record.h"
39 #include "src/trace_processor/importers/perf/perf_counter.h"
40 #include "src/trace_processor/importers/perf/perf_event.h"
41 #include "src/trace_processor/importers/perf/perf_event_attr.h"
42 #include "src/trace_processor/importers/perf/perf_tracker.h"
43 #include "src/trace_processor/importers/perf/reader.h"
44 #include "src/trace_processor/importers/perf/record.h"
45 #include "src/trace_processor/importers/perf/sample.h"
46 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
47 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
48 #include "src/trace_processor/storage/stats.h"
49 #include "src/trace_processor/storage/trace_storage.h"
50 #include "src/trace_processor/tables/metadata_tables_py.h"
51 #include "src/trace_processor/tables/profiler_tables_py.h"
52 #include "src/trace_processor/util/build_id.h"
53 #include "src/trace_processor/util/status_macros.h"
54
55 #include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
56
57 namespace perfetto::trace_processor::perf_importer {
58 namespace {
59
BuildCreateMappingParams(const CommonMmapRecordFields & fields,std::string filename,std::optional<BuildId> build_id)60 CreateMappingParams BuildCreateMappingParams(
61 const CommonMmapRecordFields& fields,
62 std::string filename,
63 std::optional<BuildId> build_id) {
64 return {AddressRange::FromStartAndSize(fields.addr, fields.len), fields.pgoff,
65 // start_offset: This is the offset into the file where the ELF header
66 // starts. We assume all file mappings are ELF files an thus this
67 // offset is 0.
68 0,
69 // load_bias: This can only be read out of the actual ELF file, which
70 // we do not have here, so we set it to 0. When symbolizing we will
71 // hopefully have the real load bias and we can compensate there for a
72 // possible mismatch.
73 0, std::move(filename), std::move(build_id)};
74 }
75
IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode)76 bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
77 switch (cpu_mode) {
78 case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
79 case protos::pbzero::Profiling::MODE_KERNEL:
80 return true;
81 case protos::pbzero::Profiling::MODE_USER:
82 case protos::pbzero::Profiling::MODE_HYPERVISOR:
83 case protos::pbzero::Profiling::MODE_GUEST_USER:
84 case protos::pbzero::Profiling::MODE_UNKNOWN:
85 return false;
86 }
87 PERFETTO_FATAL("For GCC.");
88 }
89
90 } // namespace
91
92 using FramesTable = tables::StackProfileFrameTable;
93 using CallsitesTable = tables::StackProfileCallsiteTable;
94
RecordParser(TraceProcessorContext * context)95 RecordParser::RecordParser(TraceProcessorContext* context)
96 : context_(context), mapping_tracker_(context->mapping_tracker.get()) {}
97
98 RecordParser::~RecordParser() = default;
99
ParsePerfRecord(int64_t ts,Record record)100 void RecordParser::ParsePerfRecord(int64_t ts, Record record) {
101 if (base::Status status = ParseRecord(ts, std::move(record)); !status.ok()) {
102 context_->storage->IncrementIndexedStats(
103 stats::perf_record_skipped, static_cast<int>(record.header.type));
104 }
105 }
106
ParseRecord(int64_t ts,Record record)107 base::Status RecordParser::ParseRecord(int64_t ts, Record record) {
108 switch (record.header.type) {
109 case PERF_RECORD_COMM:
110 return ParseComm(std::move(record));
111
112 case PERF_RECORD_SAMPLE:
113 return ParseSample(ts, std::move(record));
114
115 case PERF_RECORD_MMAP:
116 return ParseMmap(ts, std::move(record));
117
118 case PERF_RECORD_MMAP2:
119 return ParseMmap2(ts, std::move(record));
120
121 case PERF_RECORD_ITRACE_START:
122 return ParseItraceStart(std::move(record));
123
124 case PERF_RECORD_AUX:
125 case PERF_RECORD_AUXTRACE:
126 case PERF_RECORD_AUXTRACE_INFO:
127 // These should be dealt with at tokenization time
128 PERFETTO_FATAL("Unexpected record type at parsing time: %" PRIu32,
129 record.header.type);
130
131 default:
132 context_->storage->IncrementIndexedStats(
133 stats::perf_unknown_record_type,
134 static_cast<int>(record.header.type));
135 return base::ErrStatus("Unknown PERF_RECORD with type %" PRIu32,
136 record.header.type);
137 }
138 }
139
ParseSample(int64_t ts,Record record)140 base::Status RecordParser::ParseSample(int64_t ts, Record record) {
141 Sample sample;
142 RETURN_IF_ERROR(sample.Parse(ts, record));
143
144 if (!sample.period.has_value() && record.attr != nullptr) {
145 sample.period = record.attr->sample_period();
146 }
147
148 return InternSample(std::move(sample));
149 }
150
InternSample(Sample sample)151 base::Status RecordParser::InternSample(Sample sample) {
152 if (!sample.time.has_value()) {
153 // We do not really use this TS as this is using the perf clock, but we need
154 // it to be present so that we can compute the trace_ts done during
155 // tokenization. (Actually at tokenization time we do estimate a trace_ts if
156 // no perf ts is present, but for samples we want this to be as accurate as
157 // possible)
158 return base::ErrStatus(
159 "Can not parse samples with no PERF_SAMPLE_TIME field");
160 }
161
162 if (!sample.pid_tid.has_value()) {
163 return base::ErrStatus(
164 "Can not parse samples with no PERF_SAMPLE_TID field");
165 }
166
167 if (sample.cpu_mode ==
168 protos::pbzero::perfetto_pbzero_enum_Profiling::MODE_UNKNOWN) {
169 context_->storage->IncrementStats(stats::perf_samples_cpu_mode_unknown);
170 }
171
172 UniqueTid utid = context_->process_tracker->UpdateThread(sample.pid_tid->tid,
173 sample.pid_tid->pid);
174 const auto upid = *context_->storage->thread_table()
175 .FindById(tables::ThreadTable::Id(utid))
176 ->upid();
177
178 if (sample.callchain.empty() && sample.ip.has_value()) {
179 sample.callchain.push_back(Sample::Frame{sample.cpu_mode, *sample.ip});
180 }
181 std::optional<CallsiteId> callsite_id = InternCallchain(
182 upid, sample.callchain, sample.perf_session->needs_pc_adjustment());
183
184 context_->storage->mutable_perf_sample_table()->Insert(
185 {sample.trace_ts, utid, sample.cpu,
186 context_->storage->InternString(
187 ProfilePacketUtils::StringifyCpuMode(sample.cpu_mode)),
188 callsite_id, std::nullopt, sample.perf_session->perf_session_id()});
189
190 return UpdateCounters(sample);
191 }
192
InternCallchain(UniquePid upid,const std::vector<Sample::Frame> & callchain,bool adjust_pc)193 std::optional<CallsiteId> RecordParser::InternCallchain(
194 UniquePid upid,
195 const std::vector<Sample::Frame>& callchain,
196 bool adjust_pc) {
197 if (callchain.empty()) {
198 return std::nullopt;
199 }
200
201 auto& stack_profile_tracker = *context_->stack_profile_tracker;
202
203 std::optional<CallsiteId> parent;
204 uint32_t depth = 0;
205 // Note callchain is not empty so this is always valid.
206 const auto leaf = --callchain.rend();
207 for (auto it = callchain.rbegin(); it != callchain.rend(); ++it) {
208 uint64_t ip = it->ip;
209
210 // For non leaf frames the ip stored in the chain is the return address, but
211 // what we really need is the address of the call instruction. For that we
212 // just need to move the ip one instruction back. Instructions can be of
213 // different sizes depending on the CPU arch (ARM, AARCH64, etc..). For
214 // symbolization to work we don't really need to point at the first byte of
215 // the instruction, any byte of the instruction seems to be enough, so use
216 // -1.
217 if (ip != 0 && it != leaf && adjust_pc) {
218 --ip;
219 }
220
221 VirtualMemoryMapping* mapping;
222 if (IsInKernel(it->cpu_mode)) {
223 mapping = mapping_tracker_->FindKernelMappingForAddress(ip);
224 } else {
225 mapping = mapping_tracker_->FindUserMappingForAddress(upid, ip);
226 }
227
228 if (!mapping) {
229 context_->storage->IncrementStats(stats::perf_dummy_mapping_used);
230 // Simpleperf will not create mappings for anonymous executable mappings
231 // which are used by JITted code (e.g. V8 JavaScript).
232 mapping = GetDummyMapping(upid);
233 }
234
235 const FrameId frame_id =
236 mapping->InternFrame(mapping->ToRelativePc(ip), "");
237
238 parent = stack_profile_tracker.InternCallsite(parent, frame_id, depth);
239 depth++;
240 }
241 return parent;
242 }
243
ParseComm(Record record)244 base::Status RecordParser::ParseComm(Record record) {
245 Reader reader(record.payload.copy());
246 uint32_t pid;
247 uint32_t tid;
248 std::string comm;
249 if (!reader.Read(pid) || !reader.Read(tid) || !reader.ReadCString(comm)) {
250 return base::ErrStatus("Failed to parse PERF_RECORD_COMM");
251 }
252
253 context_->process_tracker->UpdateThread(tid, pid);
254 context_->process_tracker->UpdateThreadName(
255 tid, context_->storage->InternString(base::StringView(comm)),
256 ThreadNamePriority::kFtrace);
257
258 return base::OkStatus();
259 }
260
ParseMmap(int64_t trace_ts,Record record)261 base::Status RecordParser::ParseMmap(int64_t trace_ts, Record record) {
262 MmapRecord mmap;
263 RETURN_IF_ERROR(mmap.Parse(record));
264 std::optional<BuildId> build_id =
265 record.session->LookupBuildId(mmap.pid, mmap.filename);
266
267 auto params =
268 BuildCreateMappingParams(mmap, mmap.filename, std::move(build_id));
269
270 if (IsInKernel(record.GetCpuMode())) {
271 PerfTracker::GetOrCreate(context_)->CreateKernelMemoryMapping(
272 trace_ts, std::move(params));
273 } else {
274 PerfTracker::GetOrCreate(context_)->CreateUserMemoryMapping(
275 trace_ts, GetUpid(mmap), std::move(params));
276 }
277 return base::OkStatus();
278 }
279
ParseMmap2(int64_t trace_ts,Record record)280 base::Status RecordParser::ParseMmap2(int64_t trace_ts, Record record) {
281 Mmap2Record mmap2;
282 RETURN_IF_ERROR(mmap2.Parse(record));
283 std::optional<BuildId> build_id = mmap2.GetBuildId();
284 if (!build_id.has_value()) {
285 build_id = record.session->LookupBuildId(mmap2.pid, mmap2.filename);
286 }
287
288 auto params =
289 BuildCreateMappingParams(mmap2, mmap2.filename, std::move(build_id));
290
291 if (IsInKernel(record.GetCpuMode())) {
292 PerfTracker::GetOrCreate(context_)->CreateKernelMemoryMapping(
293 trace_ts, std::move(params));
294 } else {
295 PerfTracker::GetOrCreate(context_)->CreateUserMemoryMapping(
296 trace_ts, GetUpid(mmap2), std::move(params));
297 }
298
299 return base::OkStatus();
300 }
301
ParseItraceStart(Record record)302 base::Status RecordParser::ParseItraceStart(Record record) {
303 ItraceStartRecord start;
304 RETURN_IF_ERROR(start.Parse(record));
305 context_->process_tracker->UpdateThread(start.tid, start.pid);
306 return base::OkStatus();
307 }
308
GetUpid(const CommonMmapRecordFields & fields) const309 UniquePid RecordParser::GetUpid(const CommonMmapRecordFields& fields) const {
310 UniqueTid utid =
311 context_->process_tracker->UpdateThread(fields.tid, fields.pid);
312 auto upid = context_->storage->thread_table()
313 .FindById(tables::ThreadTable::Id(utid))
314 ->upid();
315 PERFETTO_CHECK(upid.has_value());
316 return *upid;
317 }
318
UpdateCounters(const Sample & sample)319 base::Status RecordParser::UpdateCounters(const Sample& sample) {
320 if (!sample.read_groups.empty()) {
321 return UpdateCountersInReadGroups(sample);
322 }
323
324 if (!sample.cpu.has_value()) {
325 context_->storage->IncrementStats(
326 stats::perf_counter_skipped_because_no_cpu);
327 return base::OkStatus();
328 }
329
330 if (!sample.period.has_value() && !sample.attr->sample_period().has_value()) {
331 return base::ErrStatus("No period for sample");
332 }
333
334 uint64_t period = sample.period.has_value() ? *sample.period
335 : *sample.attr->sample_period();
336 sample.attr->GetOrCreateCounter(*sample.cpu)
337 .AddDelta(sample.trace_ts, static_cast<double>(period));
338 return base::OkStatus();
339 }
340
UpdateCountersInReadGroups(const Sample & sample)341 base::Status RecordParser::UpdateCountersInReadGroups(const Sample& sample) {
342 if (!sample.cpu.has_value()) {
343 context_->storage->IncrementStats(
344 stats::perf_counter_skipped_because_no_cpu);
345 return base::OkStatus();
346 }
347
348 for (const auto& entry : sample.read_groups) {
349 RefPtr<PerfEventAttr> attr =
350 sample.perf_session->FindAttrForEventId(*entry.event_id);
351 if (PERFETTO_UNLIKELY(!attr)) {
352 return base::ErrStatus("No perf_event_attr for id %" PRIu64,
353 *entry.event_id);
354 }
355 attr->GetOrCreateCounter(*sample.cpu)
356 .AddCount(sample.trace_ts, static_cast<double>(entry.value));
357 }
358 return base::OkStatus();
359 }
360
GetDummyMapping(UniquePid upid)361 DummyMemoryMapping* RecordParser::GetDummyMapping(UniquePid upid) {
362 if (auto it = dummy_mappings_.Find(upid); it) {
363 return *it;
364 }
365
366 DummyMemoryMapping* mapping = &mapping_tracker_->CreateDummyMapping("");
367 dummy_mappings_.Insert(upid, mapping);
368 return mapping;
369 }
370
371 } // namespace perfetto::trace_processor::perf_importer
372