xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/perf/record_parser.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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