1 /*
2 * Copyright (C) 2019 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/ftrace/ftrace_sched_event_tracker.h"
18
19 #include <cstddef>
20 #include <cstdint>
21 #include <limits>
22 #include <optional>
23
24 #include "perfetto/base/logging.h"
25 #include "perfetto/ext/base/string_view.h"
26 #include "perfetto/public/compiler.h"
27 #include "src/trace_processor/importers/common/args_tracker.h"
28 #include "src/trace_processor/importers/common/event_tracker.h"
29 #include "src/trace_processor/importers/common/process_tracker.h"
30 #include "src/trace_processor/importers/common/sched_event_state.h"
31 #include "src/trace_processor/importers/common/sched_event_tracker.h"
32 #include "src/trace_processor/importers/common/system_info_tracker.h"
33 #include "src/trace_processor/importers/common/thread_state_tracker.h"
34 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
35 #include "src/trace_processor/storage/stats.h"
36 #include "src/trace_processor/storage/trace_storage.h"
37 #include "src/trace_processor/tables/metadata_tables_py.h"
38 #include "src/trace_processor/types/task_state.h"
39 #include "src/trace_processor/types/trace_processor_context.h"
40 #include "src/trace_processor/types/variadic.h"
41
42 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
43 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
44 #include "src/trace_processor/types/version_number.h"
45
46 namespace perfetto::trace_processor {
47
FtraceSchedEventTracker(TraceProcessorContext * context)48 FtraceSchedEventTracker::FtraceSchedEventTracker(TraceProcessorContext* context)
49 : context_(context) {
50 // pre-parse sched_switch
51 auto* switch_descriptor = GetMessageDescriptorForId(
52 protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber);
53 PERFETTO_CHECK(switch_descriptor->max_field_id == kSchedSwitchMaxFieldId);
54
55 for (size_t i = 1; i <= kSchedSwitchMaxFieldId; i++) {
56 sched_switch_field_ids_[i] =
57 context->storage->InternString(switch_descriptor->fields[i].name);
58 }
59 sched_switch_id_ = context->storage->InternString(switch_descriptor->name);
60
61 // pre-parse sched_waking
62 auto* waking_descriptor = GetMessageDescriptorForId(
63 protos::pbzero::FtraceEvent::kSchedWakingFieldNumber);
64 PERFETTO_CHECK(waking_descriptor->max_field_id == kSchedWakingMaxFieldId);
65
66 for (size_t i = 1; i <= kSchedWakingMaxFieldId; i++) {
67 sched_waking_field_ids_[i] =
68 context->storage->InternString(waking_descriptor->fields[i].name);
69 }
70 sched_waking_id_ = context->storage->InternString(waking_descriptor->name);
71 }
72
73 FtraceSchedEventTracker::~FtraceSchedEventTracker() = default;
74
PushSchedSwitch(uint32_t cpu,int64_t ts,uint32_t prev_pid,base::StringView prev_comm,int32_t prev_prio,int64_t prev_state,uint32_t next_pid,base::StringView next_comm,int32_t next_prio)75 void FtraceSchedEventTracker::PushSchedSwitch(uint32_t cpu,
76 int64_t ts,
77 uint32_t prev_pid,
78 base::StringView prev_comm,
79 int32_t prev_prio,
80 int64_t prev_state,
81 uint32_t next_pid,
82 base::StringView next_comm,
83 int32_t next_prio) {
84 StringId next_comm_id = context_->storage->InternString(next_comm);
85 UniqueTid next_utid = context_->process_tracker->UpdateThreadName(
86 next_pid, next_comm_id, ThreadNamePriority::kFtrace);
87
88 // First use this data to close the previous slice.
89 bool prev_pid_match_prev_next_pid = false;
90 auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
91 uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
92 StringId prev_state_string_id = TaskStateToStringId(prev_state);
93 if (prev_state_string_id == kNullStringId) {
94 context_->storage->IncrementStats(stats::task_state_invalid);
95 }
96 if (pending_slice_idx < std::numeric_limits<uint32_t>::max()) {
97 prev_pid_match_prev_next_pid = prev_pid == pending_sched->last_pid;
98 if (PERFETTO_LIKELY(prev_pid_match_prev_next_pid)) {
99 context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
100 prev_state_string_id);
101 } else {
102 // If the pids are not consistent, make a note of this.
103 context_->storage->IncrementStats(stats::mismatched_sched_switch_tids);
104 }
105 }
106
107 // We have to intern prev_comm again because our assumption that
108 // this event's |prev_comm| == previous event's |next_comm| does not hold
109 // if the thread changed its name while scheduled.
110 StringId prev_comm_id = context_->storage->InternString(prev_comm);
111 UniqueTid prev_utid = context_->process_tracker->UpdateThreadName(
112 prev_pid, prev_comm_id, ThreadNamePriority::kFtrace);
113
114 AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
115 prev_state, next_pid, next_comm_id, next_prio);
116
117 auto new_slice_idx = context_->sched_event_tracker->AddStartSlice(
118 cpu, ts, next_utid, next_prio);
119
120 // Finally, update the info for the next sched switch on this CPU.
121 pending_sched->pending_slice_storage_idx = new_slice_idx;
122 pending_sched->last_pid = next_pid;
123 pending_sched->last_utid = next_utid;
124 pending_sched->last_prio = next_prio;
125
126 // Update the ThreadState table.
127 ThreadStateTracker::GetOrCreate(context_)->PushSchedSwitchEvent(
128 ts, cpu, prev_utid, prev_state_string_id, next_utid);
129 }
130
PushSchedSwitchCompact(uint32_t cpu,int64_t ts,int64_t prev_state,uint32_t next_pid,int32_t next_prio,StringId next_comm_id,bool parse_only_into_raw)131 void FtraceSchedEventTracker::PushSchedSwitchCompact(uint32_t cpu,
132 int64_t ts,
133 int64_t prev_state,
134 uint32_t next_pid,
135 int32_t next_prio,
136 StringId next_comm_id,
137 bool parse_only_into_raw) {
138 UniqueTid next_utid = context_->process_tracker->UpdateThreadName(
139 next_pid, next_comm_id, ThreadNamePriority::kFtrace);
140
141 // If we're processing the first compact event for this cpu, don't start a
142 // slice since we're missing the "prev_*" fields. The successive events will
143 // create slices as normal, but the first per-cpu switch is effectively
144 // discarded.
145 auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
146 if (pending_sched->last_utid == std::numeric_limits<UniqueTid>::max()) {
147 context_->storage->IncrementStats(stats::compact_sched_switch_skipped);
148
149 pending_sched->last_pid = next_pid;
150 pending_sched->last_utid = next_utid;
151 pending_sched->last_prio = next_prio;
152 // Note: no pending slice, so leave |pending_slice_storage_idx| in its
153 // invalid state.
154 return;
155 }
156
157 // Close the pending slice if any (we won't have one when processing the first
158 // two compact events for a given cpu).
159 uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
160 StringId prev_state_str_id = TaskStateToStringId(prev_state);
161 if (prev_state_str_id == kNullStringId) {
162 context_->storage->IncrementStats(stats::task_state_invalid);
163 }
164 if (pending_slice_idx != std::numeric_limits<uint32_t>::max()) {
165 context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
166 prev_state_str_id);
167 }
168
169 // Use the previous event's values to infer this event's "prev_*" fields.
170 // There are edge cases, but this assumption should still produce sensible
171 // results in the absence of data loss.
172 UniqueTid prev_utid = pending_sched->last_utid;
173 uint32_t prev_pid = pending_sched->last_pid;
174 int32_t prev_prio = pending_sched->last_prio;
175
176 // Do a fresh task name lookup in case it was updated by a task_rename while
177 // scheduled.
178 StringId prev_comm_id =
179 context_->storage->thread_table()[prev_utid].name().value_or(
180 kNullStringId);
181
182 AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
183 prev_state, next_pid, next_comm_id, next_prio);
184
185 // Update the info for the next sched switch on this CPU.
186 pending_sched->last_pid = next_pid;
187 pending_sched->last_utid = next_utid;
188 pending_sched->last_prio = next_prio;
189
190 // Subtle: if only inserting into raw, we're ending with:
191 // * updated |pending_sched->last_*| fields
192 // * still-defaulted |pending_slice_storage_idx|
193 // This is similar to the first compact_sched_switch per cpu.
194 if (PERFETTO_UNLIKELY(parse_only_into_raw))
195 return;
196
197 // Update per-cpu Sched table.
198 auto new_slice_idx = context_->sched_event_tracker->AddStartSlice(
199 cpu, ts, next_utid, next_prio);
200 pending_sched->pending_slice_storage_idx = new_slice_idx;
201
202 // Update the per-thread ThreadState table.
203 ThreadStateTracker::GetOrCreate(context_)->PushSchedSwitchEvent(
204 ts, cpu, prev_utid, prev_state_str_id, next_utid);
205 }
206
207 // Processes a sched_waking that was decoded from a compact representation,
208 // adding to the raw and instants tables.
PushSchedWakingCompact(uint32_t cpu,int64_t ts,uint32_t wakee_pid,uint16_t target_cpu,uint16_t prio,StringId comm_id,uint16_t common_flags,bool parse_only_into_raw)209 void FtraceSchedEventTracker::PushSchedWakingCompact(uint32_t cpu,
210 int64_t ts,
211 uint32_t wakee_pid,
212 uint16_t target_cpu,
213 uint16_t prio,
214 StringId comm_id,
215 uint16_t common_flags,
216 bool parse_only_into_raw) {
217 // We infer the task that emitted the event (i.e. common_pid) from the
218 // scheduling slices. Drop the event if we haven't seen any sched_switch
219 // events for this cpu yet.
220 // Note that if sched_switch wasn't enabled, we will have to skip all
221 // compact waking events.
222 auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
223 if (pending_sched->last_utid == std::numeric_limits<UniqueTid>::max()) {
224 context_->storage->IncrementStats(stats::compact_sched_waking_skipped);
225 return;
226 }
227 auto curr_utid = pending_sched->last_utid;
228
229 if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) {
230 tables::FtraceEventTable::Row row;
231 row.ts = ts;
232 row.name = sched_waking_id_;
233 row.utid = curr_utid;
234 row.common_flags = common_flags;
235 row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
236
237 // Add an entry to the raw table.
238 tables::FtraceEventTable::Id id =
239 context_->storage->mutable_ftrace_event_table()->Insert(row).id;
240
241 using SW = protos::pbzero::SchedWakingFtraceEvent;
242 auto inserter = context_->args_tracker->AddArgsTo(id);
243 auto add_raw_arg = [this, &inserter](int field_num, Variadic var) {
244 StringId key = sched_waking_field_ids_[static_cast<size_t>(field_num)];
245 inserter.AddArg(key, var);
246 };
247 add_raw_arg(SW::kCommFieldNumber, Variadic::String(comm_id));
248 add_raw_arg(SW::kPidFieldNumber, Variadic::Integer(wakee_pid));
249 add_raw_arg(SW::kPrioFieldNumber, Variadic::Integer(prio));
250 add_raw_arg(SW::kTargetCpuFieldNumber, Variadic::Integer(target_cpu));
251 }
252
253 if (PERFETTO_UNLIKELY(parse_only_into_raw))
254 return;
255
256 // Add a waking entry to the ThreadState table.
257 auto wakee_utid = context_->process_tracker->GetOrCreateThread(wakee_pid);
258 ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(
259 ts, wakee_utid, curr_utid, common_flags);
260 }
261
AddRawSchedSwitchEvent(uint32_t cpu,int64_t ts,UniqueTid prev_utid,uint32_t prev_pid,StringId prev_comm_id,int32_t prev_prio,int64_t prev_state,uint32_t next_pid,StringId next_comm_id,int32_t next_prio)262 void FtraceSchedEventTracker::AddRawSchedSwitchEvent(uint32_t cpu,
263 int64_t ts,
264 UniqueTid prev_utid,
265 uint32_t prev_pid,
266 StringId prev_comm_id,
267 int32_t prev_prio,
268 int64_t prev_state,
269 uint32_t next_pid,
270 StringId next_comm_id,
271 int32_t next_prio) {
272 if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) {
273 // Push the raw event - this is done as the raw ftrace event codepath does
274 // not insert sched_switch.
275 auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
276 tables::FtraceEventTable::Id id =
277 context_->storage->mutable_ftrace_event_table()
278 ->Insert({ts, sched_switch_id_, prev_utid, {}, {}, ucpu})
279 .id;
280
281 // Note: this ordering is important. The events should be pushed in the same
282 // order as the order of fields in the proto; this is used by the raw table
283 // to index these events using the field ids.
284 using SS = protos::pbzero::SchedSwitchFtraceEvent;
285
286 auto inserter = context_->args_tracker->AddArgsTo(id);
287 auto add_raw_arg = [this, &inserter](int field_num, Variadic var) {
288 StringId key = sched_switch_field_ids_[static_cast<size_t>(field_num)];
289 inserter.AddArg(key, var);
290 };
291 add_raw_arg(SS::kPrevCommFieldNumber, Variadic::String(prev_comm_id));
292 add_raw_arg(SS::kPrevPidFieldNumber, Variadic::Integer(prev_pid));
293 add_raw_arg(SS::kPrevPrioFieldNumber, Variadic::Integer(prev_prio));
294 add_raw_arg(SS::kPrevStateFieldNumber, Variadic::Integer(prev_state));
295 add_raw_arg(SS::kNextCommFieldNumber, Variadic::String(next_comm_id));
296 add_raw_arg(SS::kNextPidFieldNumber, Variadic::Integer(next_pid));
297 add_raw_arg(SS::kNextPrioFieldNumber, Variadic::Integer(next_prio));
298 }
299 }
300
TaskStateToStringId(int64_t task_state_int)301 StringId FtraceSchedEventTracker::TaskStateToStringId(int64_t task_state_int) {
302 using ftrace_utils::TaskState;
303 std::optional<VersionNumber> kernel_version =
304 SystemInfoTracker::GetOrCreate(context_)->GetKernelVersion();
305
306 TaskState task_state = TaskState::FromRawPrevState(
307 static_cast<uint16_t>(task_state_int), kernel_version);
308 return task_state.is_valid()
309 ? context_->storage->InternString(task_state.ToString().data())
310 : kNullStringId;
311 }
312
313 } // namespace perfetto::trace_processor
314