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 <cstdint>
20 #include <optional>
21 #include <string>
22 #include <vector>
23
24 #include "perfetto/base/logging.h"
25 #include "perfetto/base/status.h"
26 #include "perfetto/ext/base/string_view.h"
27 #include "perfetto/public/compiler.h"
28 #include "perfetto/trace_processor/ref_counted.h"
29 #include "src/trace_processor/importers/common/mapping_tracker.h"
30 #include "src/trace_processor/importers/common/process_tracker.h"
31 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
32 #include "src/trace_processor/importers/perf/perf_counter.h"
33 #include "src/trace_processor/importers/perf/perf_event.h"
34 #include "src/trace_processor/importers/perf/perf_event_attr.h"
35 #include "src/trace_processor/importers/perf/reader.h"
36 #include "src/trace_processor/importers/perf/record.h"
37 #include "src/trace_processor/importers/perf/sample.h"
38 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
39 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
40 #include "src/trace_processor/storage/stats.h"
41 #include "src/trace_processor/storage/trace_storage.h"
42 #include "src/trace_processor/tables/profiler_tables_py.h"
43 #include "src/trace_processor/util/build_id.h"
44 #include "src/trace_processor/util/status_macros.h"
45
46 namespace perfetto {
47 namespace trace_processor {
48 namespace perf_importer {
49 namespace {
50
BuildCreateMappingParams(const CommonMmapRecordFields & fields,std::string filename,std::optional<BuildId> build_id)51 CreateMappingParams BuildCreateMappingParams(
52 const CommonMmapRecordFields& fields,
53 std::string filename,
54 std::optional<BuildId> build_id) {
55 return {AddressRange::FromStartAndSize(fields.addr, fields.len), fields.pgoff,
56 // start_offset: This is the offset into the file where the ELF header
57 // starts. We assume all file mappings are ELF files an thus this
58 // offset is 0.
59 0,
60 // load_bias: This can only be read out of the actual ELF file, which
61 // we do not have here, so we set it to 0. When symbolizing we will
62 // hopefully have the real load bias and we can compensate there for a
63 // possible mismatch.
64 0, std::move(filename), std::move(build_id)};
65 }
66
IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode)67 bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
68 switch (cpu_mode) {
69 case protos::pbzero::Profiling::MODE_UNKNOWN:
70 PERFETTO_FATAL("Unknown CPU mode");
71 case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
72 case protos::pbzero::Profiling::MODE_KERNEL:
73 return true;
74 case protos::pbzero::Profiling::MODE_USER:
75 case protos::pbzero::Profiling::MODE_HYPERVISOR:
76 case protos::pbzero::Profiling::MODE_GUEST_USER:
77 return false;
78 }
79 PERFETTO_FATAL("For GCC.");
80 }
81
82 } // namespace
83
84 using FramesTable = tables::StackProfileFrameTable;
85 using CallsitesTable = tables::StackProfileCallsiteTable;
86
RecordParser(TraceProcessorContext * context)87 RecordParser::RecordParser(TraceProcessorContext* context)
88 : context_(context) {}
89
90 RecordParser::~RecordParser() = default;
91
ParsePerfRecord(int64_t ts,Record record)92 void RecordParser::ParsePerfRecord(int64_t ts, Record record) {
93 if (base::Status status = ParseRecord(ts, std::move(record)); !status.ok()) {
94 context_->storage->IncrementStats(record.header.type == PERF_RECORD_SAMPLE
95 ? stats::perf_samples_skipped
96 : stats::perf_record_skipped);
97 }
98 }
99
ParseRecord(int64_t ts,Record record)100 base::Status RecordParser::ParseRecord(int64_t ts, Record record) {
101 switch (record.header.type) {
102 case PERF_RECORD_COMM:
103 return ParseComm(std::move(record));
104
105 case PERF_RECORD_SAMPLE:
106 return ParseSample(ts, std::move(record));
107
108 case PERF_RECORD_MMAP:
109 return ParseMmap(std::move(record));
110
111 case PERF_RECORD_MMAP2:
112 return ParseMmap2(std::move(record));
113
114 case PERF_RECORD_AUX:
115 case PERF_RECORD_AUXTRACE:
116 case PERF_RECORD_AUXTRACE_INFO:
117 // These should be dealt with at tokenization time
118 PERFETTO_FATAL("Unexpected record type at parsing time: %" PRIu32,
119 record.header.type);
120
121 default:
122 context_->storage->IncrementIndexedStats(
123 stats::perf_unknown_record_type,
124 static_cast<int>(record.header.type));
125 return base::ErrStatus("Unknown PERF_RECORD with type %" PRIu32,
126 record.header.type);
127 }
128 }
129
ParseSample(int64_t ts,Record record)130 base::Status RecordParser::ParseSample(int64_t ts, Record record) {
131 Sample sample;
132 RETURN_IF_ERROR(sample.Parse(ts, record));
133
134 if (!sample.period.has_value() && record.attr != nullptr) {
135 sample.period = record.attr->sample_period();
136 }
137
138 return InternSample(std::move(sample));
139 }
140
InternSample(Sample sample)141 base::Status RecordParser::InternSample(Sample sample) {
142 if (!sample.time.has_value()) {
143 // We do not really use this TS as this is using the perf clock, but we need
144 // it to be present so that we can compute the trace_ts done during
145 // tokenization. (Actually at tokenization time we do estimate a trace_ts if
146 // no perf ts is present, but for samples we want this to be as accurate as
147 // possible)
148 base::ErrStatus("Can not parse samples with no PERF_SAMPLE_TIME field");
149 }
150
151 if (!sample.pid_tid.has_value()) {
152 base::ErrStatus("Can not parse samples with no PERF_SAMPLE_TID field");
153 }
154
155 if (!sample.cpu.has_value()) {
156 base::ErrStatus("Can not parse samples with no PERF_SAMPLE_CPU field");
157 }
158
159 UniqueTid utid = context_->process_tracker->UpdateThread(sample.pid_tid->tid,
160 sample.pid_tid->pid);
161 const auto upid = *context_->storage->thread_table()
162 .FindById(tables::ThreadTable::Id(utid))
163 ->upid();
164
165 if (sample.callchain.empty() && sample.ip.has_value()) {
166 sample.callchain.push_back(Sample::Frame{sample.cpu_mode, *sample.ip});
167 }
168 std::optional<CallsiteId> callsite_id =
169 InternCallchain(upid, sample.callchain);
170
171 context_->storage->mutable_perf_sample_table()->Insert(
172 {sample.trace_ts, utid, *sample.cpu,
173 context_->storage->InternString(
174 ProfilePacketUtils::StringifyCpuMode(sample.cpu_mode)),
175 callsite_id, std::nullopt, sample.perf_session->perf_session_id()});
176
177 return UpdateCounters(sample);
178 }
179
InternCallchain(UniquePid upid,const std::vector<Sample::Frame> & callchain)180 std::optional<CallsiteId> RecordParser::InternCallchain(
181 UniquePid upid,
182 const std::vector<Sample::Frame>& callchain) {
183 if (callchain.empty()) {
184 return std::nullopt;
185 }
186
187 auto& stack_profile_tracker = *context_->stack_profile_tracker;
188 auto& mapping_tracker = *context_->mapping_tracker;
189
190 std::optional<CallsiteId> parent;
191 uint32_t depth = 0;
192 for (auto it = callchain.rbegin(); it != callchain.rend(); ++it) {
193 VirtualMemoryMapping* mapping;
194 if (IsInKernel(it->cpu_mode)) {
195 mapping = mapping_tracker.FindKernelMappingForAddress(it->ip);
196 } else {
197 mapping = mapping_tracker.FindUserMappingForAddress(upid, it->ip);
198 }
199
200 if (!mapping) {
201 context_->storage->IncrementStats(stats::perf_dummy_mapping_used);
202 // Simpleperf will not create mappings for anonymous executable mappings
203 // which are used by JITted code (e.g. V8 JavaScript).
204 mapping = mapping_tracker.GetDummyMapping();
205 }
206
207 const FrameId frame_id =
208 mapping->InternFrame(mapping->ToRelativePc(it->ip), "");
209
210 parent = stack_profile_tracker.InternCallsite(parent, frame_id, depth);
211 depth++;
212 }
213 return parent;
214 }
215
ParseComm(Record record)216 base::Status RecordParser::ParseComm(Record record) {
217 Reader reader(record.payload.copy());
218 uint32_t pid;
219 uint32_t tid;
220 std::string comm;
221 if (!reader.Read(pid) || !reader.Read(tid) || !reader.ReadCString(comm)) {
222 return base::ErrStatus("Failed to parse PERF_RECORD_COMM");
223 }
224
225 context_->process_tracker->UpdateThread(tid, pid);
226 context_->process_tracker->UpdateThreadName(
227 tid, context_->storage->InternString(base::StringView(comm)),
228 ThreadNamePriority::kFtrace);
229
230 return base::OkStatus();
231 }
232
ParseMmap(Record record)233 base::Status RecordParser::ParseMmap(Record record) {
234 MmapRecord mmap;
235 RETURN_IF_ERROR(mmap.Parse(record));
236 std::optional<BuildId> build_id =
237 record.session->LookupBuildId(mmap.pid, mmap.filename);
238 if (IsInKernel(record.GetCpuMode())) {
239 context_->mapping_tracker->CreateKernelMemoryMapping(
240 BuildCreateMappingParams(mmap, std::move(mmap.filename),
241 std::move(build_id)));
242 return base::OkStatus();
243 }
244
245 context_->mapping_tracker->CreateUserMemoryMapping(
246 GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename),
247 std::move(build_id)));
248
249 return base::OkStatus();
250 }
251
ParseMmap2(Record record)252 util::Status RecordParser::ParseMmap2(Record record) {
253 Mmap2Record mmap2;
254 RETURN_IF_ERROR(mmap2.Parse(record));
255 std::optional<BuildId> build_id = mmap2.GetBuildId();
256 if (!build_id.has_value()) {
257 build_id = record.session->LookupBuildId(mmap2.pid, mmap2.filename);
258 }
259 if (IsInKernel(record.GetCpuMode())) {
260 context_->mapping_tracker->CreateKernelMemoryMapping(
261 BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
262 std::move(build_id)));
263 return base::OkStatus();
264 }
265
266 context_->mapping_tracker->CreateUserMemoryMapping(
267 GetUpid(mmap2), BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
268 std::move(build_id)));
269
270 return base::OkStatus();
271 }
272
GetUpid(const CommonMmapRecordFields & fields) const273 UniquePid RecordParser::GetUpid(const CommonMmapRecordFields& fields) const {
274 UniqueTid utid =
275 context_->process_tracker->UpdateThread(fields.tid, fields.pid);
276 auto upid = context_->storage->thread_table()
277 .FindById(tables::ThreadTable::Id(utid))
278 ->upid();
279 PERFETTO_CHECK(upid.has_value());
280 return *upid;
281 }
282
UpdateCounters(const Sample & sample)283 base::Status RecordParser::UpdateCounters(const Sample& sample) {
284 if (!sample.read_groups.empty()) {
285 return UpdateCountersInReadGroups(sample);
286 }
287
288 if (!sample.period.has_value() && !sample.attr->sample_period().has_value()) {
289 return base::ErrStatus("No period for sample");
290 }
291
292 uint64_t period = sample.period.has_value() ? *sample.period
293 : *sample.attr->sample_period();
294 sample.attr->GetOrCreateCounter(*sample.cpu)
295 .AddDelta(sample.trace_ts, static_cast<double>(period));
296 return base::OkStatus();
297 }
298
UpdateCountersInReadGroups(const Sample & sample)299 base::Status RecordParser::UpdateCountersInReadGroups(const Sample& sample) {
300 if (!sample.cpu.has_value()) {
301 return base::ErrStatus("No cpu for sample");
302 }
303
304 for (const auto& entry : sample.read_groups) {
305 RefPtr<const PerfEventAttr> attr =
306 sample.perf_session->FindAttrForEventId(*entry.event_id);
307 if (PERFETTO_UNLIKELY(!attr)) {
308 return base::ErrStatus("No perf_event_attr for id %" PRIu64,
309 *entry.event_id);
310 }
311 attr->GetOrCreateCounter(*sample.cpu)
312 .AddCount(sample.trace_ts, static_cast<double>(entry.value));
313 }
314 return base::OkStatus();
315 }
316
317 } // namespace perf_importer
318 } // namespace trace_processor
319 } // namespace perfetto
320