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/traced/probes/ftrace/compact_sched.h"
18
19 #include <stdint.h>
20 #include <optional>
21
22 #include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
23 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
24 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
25 #include "src/traced/probes/ftrace/event_info_constants.h"
26 #include "src/traced/probes/ftrace/ftrace_config_utils.h"
27
28 namespace perfetto {
29
30 namespace {
31
32 // Pre-parse the format of sched_switch, checking if our simplifying
33 // assumptions about possible widths/signedness hold, and record the subset
34 // of the format that will be used during parsing.
ValidateSchedSwitchFormat(const Event & event)35 std::optional<CompactSchedSwitchFormat> ValidateSchedSwitchFormat(
36 const Event& event) {
37 using protos::pbzero::SchedSwitchFtraceEvent;
38
39 CompactSchedSwitchFormat switch_format;
40 switch_format.event_id = event.ftrace_event_id;
41 switch_format.size = event.size;
42
43 bool prev_state_valid = false;
44 bool next_pid_valid = false;
45 bool next_prio_valid = false;
46 bool next_comm_valid = false;
47 for (const auto& field : event.fields) {
48 switch (field.proto_field_id) {
49 case SchedSwitchFtraceEvent::kPrevStateFieldNumber:
50 switch_format.prev_state_offset = field.ftrace_offset;
51 switch_format.prev_state_type = field.ftrace_type;
52
53 // kernel type: long
54 prev_state_valid = (field.ftrace_type == kFtraceInt32 ||
55 field.ftrace_type == kFtraceInt64);
56 break;
57
58 case SchedSwitchFtraceEvent::kNextPidFieldNumber:
59 switch_format.next_pid_offset = field.ftrace_offset;
60 switch_format.next_pid_type = field.ftrace_type;
61
62 // kernel type: pid_t
63 next_pid_valid = (field.ftrace_type == kFtracePid32);
64 break;
65
66 case SchedSwitchFtraceEvent::kNextPrioFieldNumber:
67 switch_format.next_prio_offset = field.ftrace_offset;
68 switch_format.next_prio_type = field.ftrace_type;
69
70 // kernel type: int
71 next_prio_valid = (field.ftrace_type == kFtraceInt32);
72 break;
73
74 case SchedSwitchFtraceEvent::kNextCommFieldNumber:
75 switch_format.next_comm_offset = field.ftrace_offset;
76
77 next_comm_valid =
78 (field.ftrace_type == kFtraceFixedCString &&
79 field.ftrace_size == CommInterner::kExpectedCommLength);
80 break;
81 default:
82 break;
83 }
84 }
85
86 if (!prev_state_valid || !next_pid_valid || !next_prio_valid ||
87 !next_comm_valid) {
88 return std::nullopt;
89 }
90 return std::make_optional(switch_format);
91 }
92
93 // Pre-parse the format of sched_waking, checking if our simplifying
94 // assumptions about possible widths/signedness hold, and record the subset
95 // of the format that will be used during parsing.
ValidateSchedWakingFormat(const Event & event,const std::vector<Field> & common_fields)96 std::optional<CompactSchedWakingFormat> ValidateSchedWakingFormat(
97 const Event& event,
98 const std::vector<Field>& common_fields) {
99 using protos::pbzero::FtraceEvent;
100 using protos::pbzero::SchedWakingFtraceEvent;
101
102 CompactSchedWakingFormat waking_format;
103 waking_format.event_id = event.ftrace_event_id;
104 waking_format.size = event.size;
105
106 bool pid_valid = false;
107 bool target_cpu_valid = false;
108 bool prio_valid = false;
109 bool comm_valid = false;
110 bool common_flags_valid = false;
111
112 for (const Field& field : common_fields) {
113 if (field.proto_field_id == FtraceEvent::kCommonFlagsFieldNumber) {
114 waking_format.common_flags_offset = field.ftrace_offset;
115 waking_format.common_flags_type = field.ftrace_type;
116
117 common_flags_valid = (field.ftrace_type == kFtraceUint8);
118 break;
119 }
120 }
121
122 for (const Field& field : event.fields) {
123 switch (field.proto_field_id) {
124 case SchedWakingFtraceEvent::kPidFieldNumber:
125 waking_format.pid_offset = field.ftrace_offset;
126 waking_format.pid_type = field.ftrace_type;
127
128 // kernel type: pid_t
129 pid_valid = (field.ftrace_type == kFtracePid32);
130 break;
131
132 case SchedWakingFtraceEvent::kTargetCpuFieldNumber:
133 waking_format.target_cpu_offset = field.ftrace_offset;
134 waking_format.target_cpu_type = field.ftrace_type;
135
136 // kernel type: int
137 target_cpu_valid = (field.ftrace_type == kFtraceInt32);
138 break;
139
140 case SchedWakingFtraceEvent::kPrioFieldNumber:
141 waking_format.prio_offset = field.ftrace_offset;
142 waking_format.prio_type = field.ftrace_type;
143
144 // kernel type: int
145 prio_valid = (field.ftrace_type == kFtraceInt32);
146 break;
147
148 case SchedWakingFtraceEvent::kCommFieldNumber:
149 waking_format.comm_offset = field.ftrace_offset;
150
151 comm_valid = (field.ftrace_type == kFtraceFixedCString &&
152 field.ftrace_size == CommInterner::kExpectedCommLength);
153 break;
154 default:
155 break;
156 }
157 }
158
159 if (!pid_valid || !target_cpu_valid || !prio_valid || !comm_valid ||
160 !common_flags_valid) {
161 return std::nullopt;
162 }
163 return std::make_optional(waking_format);
164 }
165
166 } // namespace
167
168 // TODO(rsavitski): could avoid looping over all events if the caller did the
169 // work to remember the relevant events (translation table construction already
170 // loops over them).
ValidateFormatForCompactSched(const std::vector<Event> & events,const std::vector<Field> & common_fields)171 CompactSchedEventFormat ValidateFormatForCompactSched(
172 const std::vector<Event>& events,
173 const std::vector<Field>& common_fields) {
174 using protos::pbzero::FtraceEvent;
175
176 std::optional<CompactSchedSwitchFormat> switch_format;
177 std::optional<CompactSchedWakingFormat> waking_format;
178 for (const Event& event : events) {
179 if (event.proto_field_id == FtraceEvent::kSchedSwitchFieldNumber) {
180 switch_format = ValidateSchedSwitchFormat(event);
181 }
182 if (event.proto_field_id == FtraceEvent::kSchedWakingFieldNumber) {
183 waking_format = ValidateSchedWakingFormat(event, common_fields);
184 }
185 }
186
187 if (switch_format.has_value() && waking_format.has_value()) {
188 return CompactSchedEventFormat{/*format_valid=*/true, switch_format.value(),
189 waking_format.value()};
190 } else {
191 PERFETTO_ELOG("Unexpected sched_switch or sched_waking format.");
192 return CompactSchedEventFormat{/*format_valid=*/false,
193 CompactSchedSwitchFormat{},
194 CompactSchedWakingFormat{}};
195 }
196 }
197
InvalidCompactSchedEventFormatForTesting()198 CompactSchedEventFormat InvalidCompactSchedEventFormatForTesting() {
199 return CompactSchedEventFormat{/*format_valid=*/false,
200 CompactSchedSwitchFormat{},
201 CompactSchedWakingFormat{}};
202 }
203
CreateCompactSchedConfig(const FtraceConfig & request,bool switch_requested,const CompactSchedEventFormat & compact_format)204 CompactSchedConfig CreateCompactSchedConfig(
205 const FtraceConfig& request,
206 bool switch_requested,
207 const CompactSchedEventFormat& compact_format) {
208 // If compile-time assumptions don't hold, we'll fall back onto encoding
209 // events individually.
210 if (!compact_format.format_valid) {
211 return CompactSchedConfig{/*enabled=*/false};
212 }
213 // Enabled unless we're not recording sched_switch, or explicitly opting out.
214 // Note: compact sched_waking depends on sched_switch (for derived
215 // common_pid), so use verbose encoding if the config requests only
216 // sched_waking.
217 const auto& compact = request.compact_sched();
218 if (!switch_requested || (compact.has_enabled() && !compact.enabled())) {
219 return CompactSchedConfig{/*enabled=*/false};
220 }
221 return CompactSchedConfig{/*enabled=*/true};
222 }
223
EnabledCompactSchedConfigForTesting()224 CompactSchedConfig EnabledCompactSchedConfigForTesting() {
225 return CompactSchedConfig{/*enabled=*/true};
226 }
227
DisabledCompactSchedConfigForTesting()228 CompactSchedConfig DisabledCompactSchedConfigForTesting() {
229 return CompactSchedConfig{/*enabled=*/false};
230 }
231
Write(protos::pbzero::FtraceEventBundle::CompactSched * compact_out) const232 void CompactSchedSwitchBuffer::Write(
233 protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const {
234 compact_out->set_switch_timestamp(timestamp_);
235 compact_out->set_switch_next_pid(next_pid_);
236 compact_out->set_switch_prev_state(prev_state_);
237 compact_out->set_switch_next_prio(next_prio_);
238 compact_out->set_switch_next_comm_index(next_comm_index_);
239 }
240
Reset()241 void CompactSchedSwitchBuffer::Reset() {
242 last_timestamp_ = 0;
243 timestamp_.Reset();
244 next_pid_.Reset();
245 prev_state_.Reset();
246 next_prio_.Reset();
247 next_comm_index_.Reset();
248 }
249
Write(protos::pbzero::FtraceEventBundle::CompactSched * compact_out) const250 void CompactSchedWakingBuffer::Write(
251 protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const {
252 compact_out->set_waking_timestamp(timestamp_);
253 compact_out->set_waking_pid(pid_);
254 compact_out->set_waking_target_cpu(target_cpu_);
255 compact_out->set_waking_prio(prio_);
256 compact_out->set_waking_comm_index(comm_index_);
257 compact_out->set_waking_common_flags(common_flags_);
258 }
259
Reset()260 void CompactSchedWakingBuffer::Reset() {
261 last_timestamp_ = 0;
262 timestamp_.Reset();
263 pid_.Reset();
264 target_cpu_.Reset();
265 prio_.Reset();
266 comm_index_.Reset();
267 common_flags_.Reset();
268 }
269
Write(protos::pbzero::FtraceEventBundle::CompactSched * compact_out) const270 void CommInterner::Write(
271 protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const {
272 for (size_t i = 0; i < interned_comms_size_; i++) {
273 compact_out->add_intern_table(interned_comms_[i].data(),
274 interned_comms_[i].size());
275 }
276 }
277
Reset()278 void CommInterner::Reset() {
279 intern_buf_write_pos_ = 0;
280 interned_comms_size_ = 0;
281 }
282
WriteAndReset(protos::pbzero::FtraceEventBundle * bundle)283 void CompactSchedBuffer::WriteAndReset(
284 protos::pbzero::FtraceEventBundle* bundle) {
285 if (switch_.size() > 0 || waking_.size() > 0) {
286 auto* compact_out = bundle->set_compact_sched();
287
288 PERFETTO_DCHECK(interner_.interned_comms_size() > 0);
289 interner_.Write(compact_out);
290
291 if (switch_.size() > 0)
292 switch_.Write(compact_out);
293
294 if (waking_.size() > 0)
295 waking_.Write(compact_out);
296 }
297 Reset();
298 }
299
Reset()300 void CompactSchedBuffer::Reset() {
301 interner_.Reset();
302 switch_.Reset();
303 waking_.Reset();
304 }
305
306 } // namespace perfetto
307