• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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