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 
21 #include "perfetto/ext/base/optional.h"
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 base::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 base::nullopt;
89   }
90   return base::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)96 base::Optional<CompactSchedWakingFormat> ValidateSchedWakingFormat(
97     const Event& event) {
98   using protos::pbzero::SchedWakingFtraceEvent;
99 
100   CompactSchedWakingFormat waking_format;
101   waking_format.event_id = event.ftrace_event_id;
102   waking_format.size = event.size;
103 
104   bool pid_valid = false;
105   bool target_cpu_valid = false;
106   bool prio_valid = false;
107   bool comm_valid = false;
108   for (const auto& field : event.fields) {
109     switch (field.proto_field_id) {
110       case SchedWakingFtraceEvent::kPidFieldNumber:
111         waking_format.pid_offset = field.ftrace_offset;
112         waking_format.pid_type = field.ftrace_type;
113 
114         // kernel type: pid_t
115         pid_valid = (field.ftrace_type == kFtracePid32);
116         break;
117 
118       case SchedWakingFtraceEvent::kTargetCpuFieldNumber:
119         waking_format.target_cpu_offset = field.ftrace_offset;
120         waking_format.target_cpu_type = field.ftrace_type;
121 
122         // kernel type: int
123         target_cpu_valid = (field.ftrace_type == kFtraceInt32);
124         break;
125 
126       case SchedWakingFtraceEvent::kPrioFieldNumber:
127         waking_format.prio_offset = field.ftrace_offset;
128         waking_format.prio_type = field.ftrace_type;
129 
130         // kernel type: int
131         prio_valid = (field.ftrace_type == kFtraceInt32);
132         break;
133 
134       case SchedWakingFtraceEvent::kCommFieldNumber:
135         waking_format.comm_offset = field.ftrace_offset;
136 
137         comm_valid = (field.ftrace_type == kFtraceFixedCString &&
138                       field.ftrace_size == CommInterner::kExpectedCommLength);
139         break;
140       default:
141         break;
142     }
143   }
144 
145   if (!pid_valid || !target_cpu_valid || !prio_valid || !comm_valid) {
146     return base::nullopt;
147   }
148   return base::make_optional(waking_format);
149 }
150 
151 }  // namespace
152 
153 // TODO(rsavitski): could avoid looping over all events if the caller did the
154 // work to remember the relevant events (translation table construction already
155 // loops over them).
ValidateFormatForCompactSched(const std::vector<Event> & events)156 CompactSchedEventFormat ValidateFormatForCompactSched(
157     const std::vector<Event>& events) {
158   using protos::pbzero::FtraceEvent;
159 
160   base::Optional<CompactSchedSwitchFormat> switch_format;
161   base::Optional<CompactSchedWakingFormat> waking_format;
162   for (const Event& event : events) {
163     if (event.proto_field_id == FtraceEvent::kSchedSwitchFieldNumber) {
164       switch_format = ValidateSchedSwitchFormat(event);
165     }
166     if (event.proto_field_id == FtraceEvent::kSchedWakingFieldNumber) {
167       waking_format = ValidateSchedWakingFormat(event);
168     }
169   }
170 
171   if (switch_format.has_value() && waking_format.has_value()) {
172     return CompactSchedEventFormat{/*format_valid=*/true, switch_format.value(),
173                                    waking_format.value()};
174   } else {
175     PERFETTO_ELOG("Unexpected sched_switch or sched_waking format.");
176     return CompactSchedEventFormat{/*format_valid=*/false,
177                                    CompactSchedSwitchFormat{},
178                                    CompactSchedWakingFormat{}};
179   }
180 }
181 
InvalidCompactSchedEventFormatForTesting()182 CompactSchedEventFormat InvalidCompactSchedEventFormatForTesting() {
183   return CompactSchedEventFormat{/*format_valid=*/false,
184                                  CompactSchedSwitchFormat{},
185                                  CompactSchedWakingFormat{}};
186 }
187 
188 // TODO(rsavitski): find the correct place in the trace for, and method of,
189 // reporting rejection of compact_sched due to compile-time assumptions not
190 // holding at runtime.
CreateCompactSchedConfig(const FtraceConfig & request,const CompactSchedEventFormat & compact_format)191 CompactSchedConfig CreateCompactSchedConfig(
192     const FtraceConfig& request,
193     const CompactSchedEventFormat& compact_format) {
194   if (!request.compact_sched().enabled())
195     return CompactSchedConfig{/*enabled=*/false};
196 
197   if (!compact_format.format_valid)
198     return CompactSchedConfig{/*enabled=*/false};
199 
200   return CompactSchedConfig{/*enabled=*/true};
201 }
202 
EnabledCompactSchedConfigForTesting()203 CompactSchedConfig EnabledCompactSchedConfigForTesting() {
204   return CompactSchedConfig{/*enabled=*/true};
205 }
206 
DisabledCompactSchedConfigForTesting()207 CompactSchedConfig DisabledCompactSchedConfigForTesting() {
208   return CompactSchedConfig{/*enabled=*/false};
209 }
210 
211 // Check size of stack-allocated bundle state.
212 static_assert(sizeof(CompactSchedBuffer) <= 1 << 18,
213               "CompactSchedBuffer's on-stack size excessively large.");
214 
Write(protos::pbzero::FtraceEventBundle::CompactSched * compact_out) const215 void CompactSchedSwitchBuffer::Write(
216     protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const {
217   compact_out->set_switch_timestamp(timestamp_);
218   compact_out->set_switch_next_pid(next_pid_);
219   compact_out->set_switch_prev_state(prev_state_);
220   compact_out->set_switch_next_prio(next_prio_);
221   compact_out->set_switch_next_comm_index(next_comm_index_);
222 }
223 
Reset()224 void CompactSchedSwitchBuffer::Reset() {
225   last_timestamp_ = 0;
226   timestamp_.Reset();
227   next_pid_.Reset();
228   prev_state_.Reset();
229   next_prio_.Reset();
230   next_comm_index_.Reset();
231 }
232 
Write(protos::pbzero::FtraceEventBundle::CompactSched * compact_out) const233 void CompactSchedWakingBuffer::Write(
234     protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const {
235   compact_out->set_waking_timestamp(timestamp_);
236   compact_out->set_waking_pid(pid_);
237   compact_out->set_waking_target_cpu(target_cpu_);
238   compact_out->set_waking_prio(prio_);
239   compact_out->set_waking_comm_index(comm_index_);
240 }
241 
Reset()242 void CompactSchedWakingBuffer::Reset() {
243   last_timestamp_ = 0;
244   timestamp_.Reset();
245   pid_.Reset();
246   target_cpu_.Reset();
247   prio_.Reset();
248   comm_index_.Reset();
249 }
250 
Write(protos::pbzero::FtraceEventBundle::CompactSched * compact_out) const251 void CommInterner::Write(
252     protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const {
253   for (size_t i = 0; i < interned_comms_size_; i++) {
254     compact_out->add_intern_table(interned_comms_[i].data(),
255                                   interned_comms_[i].size());
256   }
257 }
258 
Reset()259 void CommInterner::Reset() {
260   intern_buf_write_pos_ = 0;
261   interned_comms_size_ = 0;
262 }
263 
WriteAndReset(protos::pbzero::FtraceEventBundle * bundle)264 void CompactSchedBuffer::WriteAndReset(
265     protos::pbzero::FtraceEventBundle* bundle) {
266   if (switch_.size() > 0 || waking_.size() > 0) {
267     auto* compact_out = bundle->set_compact_sched();
268 
269     PERFETTO_DCHECK(interner_.interned_comms_size() > 0);
270     interner_.Write(compact_out);
271 
272     if (switch_.size() > 0)
273       switch_.Write(compact_out);
274 
275     if (waking_.size() > 0)
276       waking_.Write(compact_out);
277   }
278 
279   interner_.Reset();
280   switch_.Reset();
281   waking_.Reset();
282 }
283 
284 }  // namespace perfetto
285