1 /*
2 * Copyright (C) 2018 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/ftrace_config_muxer.h"
18
19 #include <stdint.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23
24 #include <algorithm>
25
26 #include "perfetto/base/utils.h"
27 #include "src/traced/probes/ftrace/atrace_wrapper.h"
28
29 namespace perfetto {
30 namespace {
31
32 // trace_clocks in preference order.
33 constexpr const char* kClocks[] = {"boot", "global", "local"};
34
35 constexpr int kDefaultPerCpuBufferSizeKb = 2 * 1024; // 2mb
36 constexpr int kMaxPerCpuBufferSizeKb = 64 * 1024; // 64mb
37
AddEventGroup(const ProtoTranslationTable * table,const std::string & group,std::set<GroupAndName> * to)38 void AddEventGroup(const ProtoTranslationTable* table,
39 const std::string& group,
40 std::set<GroupAndName>* to) {
41 const std::vector<const Event*>* events = table->GetEventsByGroup(group);
42 if (!events)
43 return;
44 for (const Event* event : *events)
45 to->insert(GroupAndName(group, event->name));
46 }
47
ReadEventsInGroupFromFs(const FtraceProcfs & ftrace_procfs,const std::string & group)48 std::set<GroupAndName> ReadEventsInGroupFromFs(
49 const FtraceProcfs& ftrace_procfs,
50 const std::string& group) {
51 std::set<std::string> names =
52 ftrace_procfs.GetEventNamesForGroup("events/" + group);
53 std::set<GroupAndName> events;
54 for (const auto& name : names)
55 events.insert(GroupAndName(group, name));
56 return events;
57 }
58
EventToStringGroupAndName(const std::string & event)59 std::pair<std::string, std::string> EventToStringGroupAndName(
60 const std::string& event) {
61 auto slash_pos = event.find("/");
62 if (slash_pos == std::string::npos)
63 return std::make_pair("", event);
64 return std::make_pair(event.substr(0, slash_pos),
65 event.substr(slash_pos + 1));
66 }
67
68 } // namespace
69
GetFtraceEvents(const FtraceConfig & request,const ProtoTranslationTable * table)70 std::set<GroupAndName> FtraceConfigMuxer::GetFtraceEvents(
71 const FtraceConfig& request,
72 const ProtoTranslationTable* table) {
73 std::set<GroupAndName> events;
74 for (const auto& config_value : request.ftrace_events()) {
75 std::string group;
76 std::string name;
77 std::tie(group, name) = EventToStringGroupAndName(config_value);
78 if (name == "*") {
79 for (const auto& event : ReadEventsInGroupFromFs(*ftrace_, group))
80 events.insert(event);
81 } else if (group.empty()) {
82 // If there is no group specified, find an event with that name and
83 // use it's group.
84 const Event* e = table->GetEventByName(name);
85 if (!e) {
86 PERFETTO_DLOG(
87 "Event doesn't exist: %s. Include the group in the config to allow "
88 "the event to be output as a generic event.",
89 name.c_str());
90 continue;
91 }
92 events.insert(GroupAndName(e->group, e->name));
93 } else {
94 events.insert(GroupAndName(group, name));
95 }
96 }
97 if (RequiresAtrace(request)) {
98 events.insert(GroupAndName("ftrace", "print"));
99
100 // Ideally we should keep this code in sync with:
101 // platform/frameworks/native/cmds/atrace/atrace.cpp
102 // It's not a disaster if they go out of sync, we can always add the ftrace
103 // categories manually server side but this is user friendly and reduces the
104 // size of the configs.
105 for (const std::string& category : request.atrace_categories()) {
106 if (category == "gfx") {
107 AddEventGroup(table, "mdss", &events);
108 AddEventGroup(table, "sde", &events);
109 continue;
110 }
111
112 if (category == "ion") {
113 events.insert(GroupAndName("kmem", "ion_alloc_buffer_start"));
114 continue;
115 }
116
117 if (category == "sched") {
118 events.insert(GroupAndName("sched", "sched_switch"));
119 events.insert(GroupAndName("sched", "sched_wakeup"));
120 events.insert(GroupAndName("sched", "sched_waking"));
121 events.insert(GroupAndName("sched", "sched_blocked_reason"));
122 events.insert(GroupAndName("sched", "sched_cpu_hotplug"));
123 events.insert(GroupAndName("sched", "sched_pi_setprio"));
124 events.insert(GroupAndName("sched", "sched_process_exit"));
125 events.insert(GroupAndName("systrace", "0"));
126 AddEventGroup(table, "cgroup", &events);
127 events.insert(GroupAndName("oom", "oom_score_adj_update"));
128 events.insert(GroupAndName("task", "task_rename"));
129 events.insert(GroupAndName("task", "task_newtask"));
130 AddEventGroup(table, "systrace", &events);
131 AddEventGroup(table, "scm", &events);
132 continue;
133 }
134
135 if (category == "irq") {
136 AddEventGroup(table, "irq", &events);
137 AddEventGroup(table, "ipi", &events);
138 continue;
139 }
140
141 if (category == "irqoff") {
142 events.insert(GroupAndName("preemptirq", "irq_enable"));
143 events.insert(GroupAndName("preemptirq", "irq_disable"));
144 continue;
145 }
146
147 if (category == "preemptoff") {
148 events.insert(GroupAndName("preemptirq", "preempt_enable"));
149 events.insert(GroupAndName("preemptirq", "preempt_disable"));
150 continue;
151 }
152
153 if (category == "i2c") {
154 AddEventGroup(table, "i2c", &events);
155 events.insert(GroupAndName("i2c", "i2c_read"));
156 events.insert(GroupAndName("i2c", "i2c_write"));
157 events.insert(GroupAndName("i2c", "i2c_result"));
158 events.insert(GroupAndName("i2c", "i2c_reply"));
159 events.insert(GroupAndName("i2c", "smbus_read"));
160 events.insert(GroupAndName("i2c", "smbus_write"));
161 events.insert(GroupAndName("i2c", "smbus_result"));
162 events.insert(GroupAndName("i2c", "smbus_reply"));
163 continue;
164 }
165
166 if (category == "freq") {
167 events.insert(GroupAndName("power", "cpu_frequency"));
168 events.insert(GroupAndName("power", "gpu_frequency"));
169 events.insert(GroupAndName("power", "clock_set_rate"));
170 events.insert(GroupAndName("power", "clock_disable"));
171 events.insert(GroupAndName("power", "clock_enable"));
172 events.insert(GroupAndName("clk", "clk_set_rate"));
173 events.insert(GroupAndName("clk", "clk_disable"));
174 events.insert(GroupAndName("clk", "clk_enable"));
175 events.insert(GroupAndName("power", "cpu_frequency_limits"));
176 AddEventGroup(table, "msm_bus", &events);
177 continue;
178 }
179
180 if (category == "membus") {
181 AddEventGroup(table, "memory_bus", &events);
182 continue;
183 }
184
185 if (category == "idle") {
186 events.insert(GroupAndName("power", "cpu_idle"));
187 continue;
188 }
189
190 if (category == "disk") {
191 events.insert(GroupAndName("f2fs", "f2fs_sync_file_enter"));
192 events.insert(GroupAndName("f2fs", "f2fs_sync_file_exit"));
193 events.insert(GroupAndName("f2fs", "f2fs_write_begin"));
194 events.insert(GroupAndName("f2fs", "f2fs_write_end"));
195 events.insert(GroupAndName("ext4", "ext4_da_write_begin"));
196 events.insert(GroupAndName("ext4", "ext4_da_write_end"));
197 events.insert(GroupAndName("ext4", "ext4_sync_file_enter"));
198 events.insert(GroupAndName("ext4", "ext4_sync_file_exit"));
199 events.insert(GroupAndName("block", "block_rq_issue"));
200 events.insert(GroupAndName("block", "block_rq_complete"));
201 continue;
202 }
203
204 if (category == "mmc") {
205 AddEventGroup(table, "mmc", &events);
206 continue;
207 }
208
209 if (category == "load") {
210 AddEventGroup(table, "cpufreq_interactive", &events);
211 continue;
212 }
213
214 if (category == "sync") {
215 // linux kernel < 4.9
216 AddEventGroup(table, "sync", &events);
217 // linux kernel == 4.9.x
218 AddEventGroup(table, "fence", &events);
219 // linux kernel > 4.9
220 AddEventGroup(table, "dma_fence", &events);
221 continue;
222 }
223
224 if (category == "workq") {
225 AddEventGroup(table, "workqueue", &events);
226 continue;
227 }
228
229 if (category == "memreclaim") {
230 events.insert(GroupAndName("vmscan", "mm_vmscan_direct_reclaim_begin"));
231 events.insert(GroupAndName("vmscan", "mm_vmscan_direct_reclaim_end"));
232 events.insert(GroupAndName("vmscan", "mm_vmscan_kswapd_wake"));
233 events.insert(GroupAndName("vmscan", "mm_vmscan_kswapd_sleep"));
234 AddEventGroup(table, "lowmemorykiller", &events);
235 continue;
236 }
237
238 if (category == "regulators") {
239 AddEventGroup(table, "regulator", &events);
240 continue;
241 }
242
243 if (category == "binder_driver") {
244 events.insert(GroupAndName("binder", "binder_transaction"));
245 events.insert(GroupAndName("binder", "binder_transaction_received"));
246 events.insert(GroupAndName("binder", "binder_transaction_alloc_buf"));
247 events.insert(GroupAndName("binder", "binder_set_priority"));
248 continue;
249 }
250
251 if (category == "binder_lock") {
252 events.insert(GroupAndName("binder", "binder_lock"));
253 events.insert(GroupAndName("binder", "binder_locked"));
254 events.insert(GroupAndName("binder", "binder_unlock"));
255 continue;
256 }
257
258 if (category == "pagecache") {
259 AddEventGroup(table, "filemap", &events);
260 continue;
261 }
262
263 if (category == "memory") {
264 events.insert(GroupAndName("kmem", "rss_stat"));
265 events.insert(GroupAndName("kmem", "ion_heap_grow"));
266 events.insert(GroupAndName("kmem", "ion_heap_shrink"));
267 continue;
268 }
269 }
270 }
271 return events;
272 }
273
274 // Post-conditions:
275 // 1. result >= 1 (should have at least one page per CPU)
276 // 2. result * 4 < kMaxTotalBufferSizeKb
277 // 3. If input is 0 output is a good default number.
ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb)278 size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb) {
279 if (requested_buffer_size_kb == 0)
280 requested_buffer_size_kb = kDefaultPerCpuBufferSizeKb;
281 if (requested_buffer_size_kb > kMaxPerCpuBufferSizeKb) {
282 PERFETTO_ELOG(
283 "The requested ftrace buf size (%zu KB) is too big, capping to %d KB",
284 requested_buffer_size_kb, kMaxPerCpuBufferSizeKb);
285 requested_buffer_size_kb = kMaxPerCpuBufferSizeKb;
286 }
287
288 size_t pages = requested_buffer_size_kb / (base::kPageSize / 1024);
289 if (pages == 0)
290 return 1;
291
292 return pages;
293 }
294
FtraceConfigMuxer(FtraceProcfs * ftrace,ProtoTranslationTable * table)295 FtraceConfigMuxer::FtraceConfigMuxer(FtraceProcfs* ftrace,
296 ProtoTranslationTable* table)
297 : ftrace_(ftrace),
298 table_(table),
299 current_state_(),
300 filters_(),
301 configs_() {}
302 FtraceConfigMuxer::~FtraceConfigMuxer() = default;
303
SetupConfig(const FtraceConfig & request)304 FtraceConfigId FtraceConfigMuxer::SetupConfig(const FtraceConfig& request) {
305 EventFilter filter;
306 FtraceConfig actual;
307 bool is_ftrace_enabled = ftrace_->IsTracingEnabled();
308 if (configs_.empty()) {
309 PERFETTO_DCHECK(active_configs_.empty());
310 PERFETTO_DCHECK(!current_state_.tracing_on);
311
312 // If someone outside of perfetto is using ftrace give up now.
313 if (is_ftrace_enabled)
314 return 0;
315
316 // Setup ftrace, without starting it. Setting buffers can be quite slow
317 // (up to hundreds of ms).
318 SetupClock(request);
319 SetupBufferSize(request);
320 } else {
321 // Did someone turn ftrace off behind our back? If so give up.
322 if (!active_configs_.empty() && !is_ftrace_enabled)
323 return 0;
324 }
325
326 std::set<GroupAndName> events = GetFtraceEvents(request, table_);
327
328 if (RequiresAtrace(request))
329 UpdateAtrace(request);
330
331 for (const auto& group_and_name : events) {
332 const Event* event = table_->GetOrCreateEvent(group_and_name);
333 if (!event) {
334 PERFETTO_DLOG("Can't enable %s, event not known",
335 group_and_name.ToString().c_str());
336 continue;
337 }
338 if (current_state_.ftrace_events.IsEventEnabled(event->ftrace_event_id) ||
339 std::string("ftrace") == event->group) {
340 filter.AddEnabledEvent(event->ftrace_event_id);
341 *actual.add_ftrace_events() = group_and_name.ToString();
342 continue;
343 }
344 if (ftrace_->EnableEvent(event->group, event->name)) {
345 current_state_.ftrace_events.AddEnabledEvent(event->ftrace_event_id);
346 filter.AddEnabledEvent(event->ftrace_event_id);
347 *actual.add_ftrace_events() = group_and_name.ToString();
348 } else {
349 PERFETTO_DPLOG("Failed to enable %s.", group_and_name.ToString().c_str());
350 }
351 }
352
353 FtraceConfigId id = ++last_id_;
354 configs_.emplace(id, std::move(actual));
355 filters_.emplace(id, std::move(filter));
356 return id;
357 }
358
ActivateConfig(FtraceConfigId id)359 bool FtraceConfigMuxer::ActivateConfig(FtraceConfigId id) {
360 if (!id || configs_.count(id) == 0) {
361 PERFETTO_DFATAL("Config not found");
362 return false;
363 }
364
365 active_configs_.insert(id);
366 if (active_configs_.size() > 1) {
367 PERFETTO_DCHECK(current_state_.tracing_on);
368 return true; // We are not the first, ftrace is already enabled. All done.
369 }
370
371 PERFETTO_DCHECK(!current_state_.tracing_on);
372 if (ftrace_->IsTracingEnabled()) {
373 // If someone outside of perfetto is using ftrace give up now.
374 return false;
375 }
376
377 ftrace_->EnableTracing();
378 current_state_.tracing_on = true;
379 return true;
380 }
381
RemoveConfig(FtraceConfigId config_id)382 bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId config_id) {
383 if (!config_id || !filters_.erase(config_id) || !configs_.erase(config_id))
384 return false;
385 EventFilter expected_ftrace_events;
386 for (const auto& id_filter : filters_) {
387 expected_ftrace_events.EnableEventsFrom(id_filter.second);
388 }
389
390 // Disable any events that are currently enabled, but are not in any configs
391 // anymore.
392 std::set<size_t> event_ids = current_state_.ftrace_events.GetEnabledEvents();
393 for (size_t id : event_ids) {
394 if (expected_ftrace_events.IsEventEnabled(id))
395 continue;
396 const Event* event = table_->GetEventById(id);
397 // Any event that was enabled must exist.
398 PERFETTO_DCHECK(event);
399 if (ftrace_->DisableEvent(event->group, event->name))
400 current_state_.ftrace_events.DisableEvent(event->ftrace_event_id);
401 }
402
403 // If there aren't any more active configs, disable ftrace.
404 auto active_it = active_configs_.find(config_id);
405 if (active_it != active_configs_.end()) {
406 PERFETTO_DCHECK(current_state_.tracing_on);
407 active_configs_.erase(active_it);
408 if (active_configs_.empty()) {
409 // This was the last active config, disable ftrace.
410 ftrace_->DisableTracing();
411 current_state_.tracing_on = false;
412 }
413 }
414
415 // Even if we don't have any other active configs, we might still have idle
416 // configs around. Tear down the rest of the ftrace config only if all
417 // configs are removed.
418 if (configs_.empty()) {
419 ftrace_->SetCpuBufferSizeInPages(0);
420 ftrace_->DisableAllEvents();
421 ftrace_->ClearTrace();
422 if (current_state_.atrace_on)
423 DisableAtrace();
424 }
425
426 return true;
427 }
428
GetConfigForTesting(FtraceConfigId id)429 const FtraceConfig* FtraceConfigMuxer::GetConfigForTesting(FtraceConfigId id) {
430 if (!configs_.count(id))
431 return nullptr;
432 return &configs_.at(id);
433 }
434
GetEventFilter(FtraceConfigId id)435 const EventFilter* FtraceConfigMuxer::GetEventFilter(FtraceConfigId id) {
436 if (!filters_.count(id))
437 return nullptr;
438 return &filters_.at(id);
439 }
440
SetupClock(const FtraceConfig &)441 void FtraceConfigMuxer::SetupClock(const FtraceConfig&) {
442 std::string current_clock = ftrace_->GetClock();
443 std::set<std::string> clocks = ftrace_->AvailableClocks();
444
445 for (size_t i = 0; i < base::ArraySize(kClocks); i++) {
446 std::string clock = std::string(kClocks[i]);
447 if (!clocks.count(clock))
448 continue;
449 if (current_clock == clock)
450 break;
451 ftrace_->SetClock(clock);
452 break;
453 }
454 }
455
SetupBufferSize(const FtraceConfig & request)456 void FtraceConfigMuxer::SetupBufferSize(const FtraceConfig& request) {
457 size_t pages = ComputeCpuBufferSizeInPages(request.buffer_size_kb());
458 ftrace_->SetCpuBufferSizeInPages(pages);
459 current_state_.cpu_buffer_size_pages = pages;
460 }
461
UpdateAtrace(const FtraceConfig & request)462 void FtraceConfigMuxer::UpdateAtrace(const FtraceConfig& request) {
463 PERFETTO_DLOG("Update atrace config...");
464
465 std::vector<std::string> args;
466 args.push_back("atrace"); // argv0 for exec()
467 args.push_back("--async_start");
468 args.push_back("--only_userspace");
469 for (const auto& category : request.atrace_categories())
470 args.push_back(category);
471 if (!request.atrace_apps().empty()) {
472 args.push_back("-a");
473 std::string arg = "";
474 for (const auto& app : request.atrace_apps()) {
475 arg += app;
476 if (app != request.atrace_apps().back())
477 arg += ",";
478 }
479 args.push_back(arg);
480 }
481
482 if (RunAtrace(args))
483 current_state_.atrace_on = true;
484
485 PERFETTO_DLOG("...done");
486 }
487
DisableAtrace()488 void FtraceConfigMuxer::DisableAtrace() {
489 PERFETTO_DCHECK(current_state_.atrace_on);
490
491 PERFETTO_DLOG("Stop atrace...");
492
493 if (RunAtrace({"atrace", "--async_stop", "--only_userspace"}))
494 current_state_.atrace_on = false;
495
496 PERFETTO_DLOG("...done");
497 }
498
499 } // namespace perfetto
500