1 /*
2 * Copyright (C) 2017 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_procfs.h"
18
19 #include <string.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23
24 #include <string>
25
26 #include "perfetto/base/logging.h"
27 #include "perfetto/ext/base/file_utils.h"
28 #include "perfetto/ext/base/string_splitter.h"
29 #include "perfetto/ext/base/string_utils.h"
30 #include "perfetto/ext/base/utils.h"
31
32 namespace perfetto {
33
34 // Reading /trace produces human readable trace output.
35 // Writing to this file clears all trace buffers for all CPUS.
36
37 // Writing to /trace_marker file injects an event into the trace buffer.
38
39 // Reading /tracing_on returns 1/0 if tracing is enabled/disabled.
40 // Writing 1/0 to this file enables/disables tracing.
41 // Disabling tracing with this file prevents further writes but
42 // does not clear the buffer.
43
44 namespace {
45
46 namespace {
47 constexpr char kRssStatThrottledTrigger[] =
48 "hist:keys=mm_id,member:bucket=size/0x80000"
49 ":onchange($bucket).rss_stat_throttled(mm_id,curr,member,size)";
50
51 constexpr char kSuspendResumeMinimalTrigger[] =
52 "hist:keys=start:size=128:onmatch(power.suspend_resume)"
53 ".trace(suspend_resume_minimal, start) if action == 'syscore_resume'";
54 } // namespace
55
KernelLogWrite(const char * s)56 void KernelLogWrite(const char* s) {
57 PERFETTO_DCHECK(*s && s[strlen(s) - 1] == '\n');
58 if (FtraceProcfs::g_kmesg_fd != -1)
59 base::ignore_result(base::WriteAll(FtraceProcfs::g_kmesg_fd, s, strlen(s)));
60 }
61
WriteFileInternal(const std::string & path,const std::string & str,int flags)62 bool WriteFileInternal(const std::string& path,
63 const std::string& str,
64 int flags) {
65 base::ScopedFile fd = base::OpenFile(path, flags);
66 if (!fd)
67 return false;
68 ssize_t written = base::WriteAll(fd.get(), str.c_str(), str.length());
69 ssize_t length = static_cast<ssize_t>(str.length());
70 // This should either fail or write fully.
71 PERFETTO_CHECK(written == length || written == -1);
72 return written == length;
73 }
74
75 } // namespace
76
77 // static
78 int FtraceProcfs::g_kmesg_fd = -1; // Set by ProbesMain() in probes.cc .
79
80 const char* const FtraceProcfs::kTracingPaths[] = {
81 "/sys/kernel/tracing/",
82 "/sys/kernel/debug/tracing/",
83 nullptr,
84 };
85
86 // static
CreateGuessingMountPoint(const std::string & instance_path)87 std::unique_ptr<FtraceProcfs> FtraceProcfs::CreateGuessingMountPoint(
88 const std::string& instance_path) {
89 std::unique_ptr<FtraceProcfs> ftrace_procfs;
90 size_t index = 0;
91 while (!ftrace_procfs && kTracingPaths[index]) {
92 std::string path = kTracingPaths[index++];
93 if (!instance_path.empty())
94 path += instance_path;
95
96 ftrace_procfs = Create(path);
97 }
98 return ftrace_procfs;
99 }
100
101 // static
Create(const std::string & root)102 std::unique_ptr<FtraceProcfs> FtraceProcfs::Create(const std::string& root) {
103 if (!CheckRootPath(root))
104 return nullptr;
105 return std::unique_ptr<FtraceProcfs>(new FtraceProcfs(root));
106 }
107
FtraceProcfs(const std::string & root)108 FtraceProcfs::FtraceProcfs(const std::string& root) : root_(root) {}
109 FtraceProcfs::~FtraceProcfs() = default;
110
SetSyscallFilter(const std::set<size_t> & filter)111 bool FtraceProcfs::SetSyscallFilter(const std::set<size_t>& filter) {
112 std::vector<std::string> parts;
113 for (size_t id : filter) {
114 base::StackString<16> m("id == %zu", id);
115 parts.push_back(m.ToStdString());
116 }
117
118 std::string filter_str = "0";
119 if (!parts.empty()) {
120 filter_str = base::Join(parts, " || ");
121 }
122
123 for (const char* event : {"sys_enter", "sys_exit"}) {
124 std::string path = root_ + "events/raw_syscalls/" + event + "/filter";
125 if (!WriteToFile(path, filter_str)) {
126 PERFETTO_ELOG("Failed to write file: %s", path.c_str());
127 return false;
128 }
129 }
130 return true;
131 }
132
EnableEvent(const std::string & group,const std::string & name)133 bool FtraceProcfs::EnableEvent(const std::string& group,
134 const std::string& name) {
135 std::string path = root_ + "events/" + group + "/" + name + "/enable";
136
137 // Create any required triggers for the ftrace event being enabled.
138 // Some ftrace events (synthetic events) need to set up an event trigger
139 MaybeSetUpEventTriggers(group, name);
140
141 if (WriteToFile(path, "1"))
142 return true;
143 path = root_ + "set_event";
144 return AppendToFile(path, group + ":" + name);
145 }
146
DisableEvent(const std::string & group,const std::string & name)147 bool FtraceProcfs::DisableEvent(const std::string& group,
148 const std::string& name) {
149 std::string path = root_ + "events/" + group + "/" + name + "/enable";
150
151 bool ret = WriteToFile(path, "0");
152 if (!ret) {
153 path = root_ + "set_event";
154 ret = AppendToFile(path, "!" + group + ":" + name);
155 }
156
157 // Remove any associated event triggers after disabling the event
158 MaybeTearDownEventTriggers(group, name);
159
160 return ret;
161 }
162
IsEventAccessible(const std::string & group,const std::string & name)163 bool FtraceProcfs::IsEventAccessible(const std::string& group,
164 const std::string& name) {
165 std::string path = root_ + "events/" + group + "/" + name + "/enable";
166
167 return IsFileWriteable(path);
168 }
169
DisableAllEvents()170 bool FtraceProcfs::DisableAllEvents() {
171 std::string path = root_ + "events/enable";
172 return WriteToFile(path, "0");
173 }
174
ReadEventFormat(const std::string & group,const std::string & name) const175 std::string FtraceProcfs::ReadEventFormat(const std::string& group,
176 const std::string& name) const {
177 std::string path = root_ + "events/" + group + "/" + name + "/format";
178 return ReadFileIntoString(path);
179 }
180
GetCurrentTracer()181 std::string FtraceProcfs::GetCurrentTracer() {
182 std::string path = root_ + "current_tracer";
183 std::string current_tracer = ReadFileIntoString(path);
184 return base::StripSuffix(current_tracer, "\n");
185 }
186
SetCurrentTracer(const std::string & tracer)187 bool FtraceProcfs::SetCurrentTracer(const std::string& tracer) {
188 std::string path = root_ + "current_tracer";
189 return WriteToFile(path, tracer);
190 }
191
ResetCurrentTracer()192 bool FtraceProcfs::ResetCurrentTracer() {
193 return SetCurrentTracer("nop");
194 }
195
AppendFunctionFilters(const std::vector<std::string> & filters)196 bool FtraceProcfs::AppendFunctionFilters(
197 const std::vector<std::string>& filters) {
198 std::string path = root_ + "set_ftrace_filter";
199 std::string filter = base::Join(filters, "\n");
200
201 // The same file accepts special actions to perform when a corresponding
202 // kernel function is hit (regardless of active tracer). For example
203 // "__schedule_bug:traceoff" would disable tracing once __schedule_bug is
204 // called.
205 // We disallow these commands as most of them break the isolation of
206 // concurrent ftrace data sources (as the underlying ftrace instance is
207 // shared).
208 if (base::Contains(filter, ':')) {
209 PERFETTO_ELOG("Filter commands are disallowed.");
210 return false;
211 }
212 return AppendToFile(path, filter);
213 }
214
ClearFunctionFilters()215 bool FtraceProcfs::ClearFunctionFilters() {
216 std::string path = root_ + "set_ftrace_filter";
217 return ClearFile(path);
218 }
219
AppendFunctionGraphFilters(const std::vector<std::string> & filters)220 bool FtraceProcfs::AppendFunctionGraphFilters(
221 const std::vector<std::string>& filters) {
222 std::string path = root_ + "set_graph_function";
223 std::string filter = base::Join(filters, "\n");
224 return AppendToFile(path, filter);
225 }
226
ClearFunctionGraphFilters()227 bool FtraceProcfs::ClearFunctionGraphFilters() {
228 std::string path = root_ + "set_graph_function";
229 return ClearFile(path);
230 }
231
ReadEventTriggers(const std::string & group,const std::string & name) const232 std::vector<std::string> FtraceProcfs::ReadEventTriggers(
233 const std::string& group,
234 const std::string& name) const {
235 std::string path = root_ + "events/" + group + "/" + name + "/trigger";
236 std::string s = ReadFileIntoString(path);
237 std::vector<std::string> triggers;
238
239 for (base::StringSplitter ss(s, '\n'); ss.Next();) {
240 std::string trigger = ss.cur_token();
241 if (trigger.empty() || trigger[0] == '#')
242 continue;
243
244 base::StringSplitter ts(trigger, ' ');
245 PERFETTO_CHECK(ts.Next());
246 triggers.push_back(ts.cur_token());
247 }
248
249 return triggers;
250 }
251
CreateEventTrigger(const std::string & group,const std::string & name,const std::string & trigger)252 bool FtraceProcfs::CreateEventTrigger(const std::string& group,
253 const std::string& name,
254 const std::string& trigger) {
255 std::string path = root_ + "events/" + group + "/" + name + "/trigger";
256 return WriteToFile(path, trigger);
257 }
258
RemoveEventTrigger(const std::string & group,const std::string & name,const std::string & trigger)259 bool FtraceProcfs::RemoveEventTrigger(const std::string& group,
260 const std::string& name,
261 const std::string& trigger) {
262 std::string path = root_ + "events/" + group + "/" + name + "/trigger";
263 return WriteToFile(path, "!" + trigger);
264 }
265
RemoveAllEventTriggers(const std::string & group,const std::string & name)266 bool FtraceProcfs::RemoveAllEventTriggers(const std::string& group,
267 const std::string& name) {
268 std::vector<std::string> triggers = ReadEventTriggers(group, name);
269
270 // Remove the triggers in reverse order since a trigger can depend
271 // on another trigger created earlier.
272 for (auto it = triggers.rbegin(); it != triggers.rend(); ++it)
273 if (!RemoveEventTrigger(group, name, *it))
274 return false;
275 return true;
276 }
277
MaybeSetUpEventTriggers(const std::string & group,const std::string & name)278 bool FtraceProcfs::MaybeSetUpEventTriggers(const std::string& group,
279 const std::string& name) {
280 bool ret = true;
281
282 if (group == "synthetic") {
283 if (name == "rss_stat_throttled") {
284 ret = RemoveAllEventTriggers("kmem", "rss_stat") &&
285 CreateEventTrigger("kmem", "rss_stat", kRssStatThrottledTrigger);
286 } else if (name == "suspend_resume_minimal") {
287 ret = RemoveAllEventTriggers("power", "suspend_resume") &&
288 CreateEventTrigger("power", "suspend_resume",
289 kSuspendResumeMinimalTrigger);
290 }
291 }
292
293 if (!ret) {
294 PERFETTO_PLOG("Failed to setup event triggers for %s:%s", group.c_str(),
295 name.c_str());
296 }
297
298 return ret;
299 }
300
MaybeTearDownEventTriggers(const std::string & group,const std::string & name)301 bool FtraceProcfs::MaybeTearDownEventTriggers(const std::string& group,
302 const std::string& name) {
303 bool ret = true;
304
305 if (group == "synthetic") {
306 if (name == "rss_stat_throttled") {
307 ret = RemoveAllEventTriggers("kmem", "rss_stat");
308 } else if (name == "suspend_resume_minimal") {
309 ret = RemoveEventTrigger("power", "suspend_resume",
310 kSuspendResumeMinimalTrigger);
311 }
312 }
313
314 if (!ret) {
315 PERFETTO_PLOG("Failed to tear down event triggers for: %s:%s",
316 group.c_str(), name.c_str());
317 }
318
319 return ret;
320 }
321
SupportsRssStatThrottled()322 bool FtraceProcfs::SupportsRssStatThrottled() {
323 std::string group = "synthetic";
324 std::string name = "rss_stat_throttled";
325
326 // Check if the trigger already exists. Don't try recreating
327 // or removing the trigger if it is already in use.
328 auto triggers = ReadEventTriggers("kmem", "rss_stat");
329 for (const auto& trigger : triggers) {
330 // The kernel shows all the default values of a trigger
331 // when read from and trace event 'trigger' file.
332 //
333 // Trying to match the complete trigger string is prone
334 // to fail if, in the future, the kernel changes default
335 // fields or values for event triggers.
336 //
337 // Do a partial match on the generated event name
338 // (rss_stat_throttled) to detect if the trigger
339 // is already created.
340 if (trigger.find(name) != std::string::npos)
341 return true;
342 }
343
344 // Attempt to create rss_stat_throttled hist trigger */
345 bool ret = MaybeSetUpEventTriggers(group, name);
346
347 return ret && MaybeTearDownEventTriggers(group, name);
348 }
349
ReadPrintkFormats() const350 std::string FtraceProcfs::ReadPrintkFormats() const {
351 std::string path = root_ + "printk_formats";
352 return ReadFileIntoString(path);
353 }
354
ReadEnabledEvents()355 std::vector<std::string> FtraceProcfs::ReadEnabledEvents() {
356 std::string path = root_ + "set_event";
357 std::string s = ReadFileIntoString(path);
358 base::StringSplitter ss(s, '\n');
359 std::vector<std::string> events;
360 while (ss.Next()) {
361 std::string event = ss.cur_token();
362 if (event.empty())
363 continue;
364 events.push_back(base::StripChars(event, ":", '/'));
365 }
366 return events;
367 }
368
ReadPageHeaderFormat() const369 std::string FtraceProcfs::ReadPageHeaderFormat() const {
370 std::string path = root_ + "events/header_page";
371 return ReadFileIntoString(path);
372 }
373
OpenCpuStats(size_t cpu) const374 base::ScopedFile FtraceProcfs::OpenCpuStats(size_t cpu) const {
375 std::string path = root_ + "per_cpu/cpu" + std::to_string(cpu) + "/stats";
376 return base::OpenFile(path, O_RDONLY);
377 }
378
ReadCpuStats(size_t cpu) const379 std::string FtraceProcfs::ReadCpuStats(size_t cpu) const {
380 std::string path = root_ + "per_cpu/cpu" + std::to_string(cpu) + "/stats";
381 return ReadFileIntoString(path);
382 }
383
NumberOfCpus() const384 size_t FtraceProcfs::NumberOfCpus() const {
385 static size_t num_cpus = static_cast<size_t>(sysconf(_SC_NPROCESSORS_CONF));
386 return num_cpus;
387 }
388
ClearTrace()389 void FtraceProcfs::ClearTrace() {
390 std::string path = root_ + "trace";
391 PERFETTO_CHECK(ClearFile(path)); // Could not clear.
392
393 // Truncating the trace file leads to tracing_reset_online_cpus being called
394 // in the kernel.
395 //
396 // In case some of the CPUs were not online, their buffer needs to be
397 // cleared manually.
398 //
399 // We cannot use PERFETTO_CHECK as we might get a permission denied error
400 // on Android. The permissions to these files are configured in
401 // platform/framework/native/cmds/atrace/atrace.rc.
402 for (size_t cpu = 0, num_cpus = NumberOfCpus(); cpu < num_cpus; cpu++) {
403 ClearPerCpuTrace(cpu);
404 }
405 }
406
ClearPerCpuTrace(size_t cpu)407 void FtraceProcfs::ClearPerCpuTrace(size_t cpu) {
408 if (!ClearFile(root_ + "per_cpu/cpu" + std::to_string(cpu) + "/trace"))
409 PERFETTO_ELOG("Failed to clear buffer for CPU %zd", cpu);
410 }
411
WriteTraceMarker(const std::string & str)412 bool FtraceProcfs::WriteTraceMarker(const std::string& str) {
413 std::string path = root_ + "trace_marker";
414 return WriteToFile(path, str);
415 }
416
SetCpuBufferSizeInPages(size_t pages)417 bool FtraceProcfs::SetCpuBufferSizeInPages(size_t pages) {
418 std::string path = root_ + "buffer_size_kb";
419 return WriteNumberToFile(path, pages * (base::GetSysPageSize() / 1024ul));
420 }
421
GetTracingOn()422 bool FtraceProcfs::GetTracingOn() {
423 std::string path = root_ + "tracing_on";
424 char tracing_on = ReadOneCharFromFile(path);
425 if (tracing_on == '\0')
426 PERFETTO_PLOG("Failed to read %s", path.c_str());
427 return tracing_on == '1';
428 }
429
SetTracingOn(bool on)430 bool FtraceProcfs::SetTracingOn(bool on) {
431 std::string path = root_ + "tracing_on";
432 if (!WriteToFile(path, on ? "1" : "0")) {
433 PERFETTO_PLOG("Failed to write %s", path.c_str());
434 return false;
435 }
436 if (on) {
437 KernelLogWrite("perfetto: enabled ftrace\n");
438 PERFETTO_LOG("enabled ftrace in %s", root_.c_str());
439 } else {
440 KernelLogWrite("perfetto: disabled ftrace\n");
441 PERFETTO_LOG("disabled ftrace in %s", root_.c_str());
442 }
443
444 return true;
445 }
446
IsTracingAvailable()447 bool FtraceProcfs::IsTracingAvailable() {
448 std::string current_tracer = GetCurrentTracer();
449
450 // Ftrace tracing is available if current_tracer == "nop".
451 // events/enable could be 0, 1, X or 0*. 0* means events would be
452 // dynamically enabled so we need to treat as event tracing is in use.
453 // However based on the discussion in asop/2328817, on Android events/enable
454 // is "X" after boot up. To avoid causing more problem, the decision is just
455 // look at current_tracer.
456 // As the discussion in asop/2328817, if GetCurrentTracer failed to
457 // read file and return "", we treat it as tracing is available.
458 return current_tracer == "nop" || current_tracer == "";
459 }
460
SetClock(const std::string & clock_name)461 bool FtraceProcfs::SetClock(const std::string& clock_name) {
462 std::string path = root_ + "trace_clock";
463 return WriteToFile(path, clock_name);
464 }
465
GetClock()466 std::string FtraceProcfs::GetClock() {
467 std::string path = root_ + "trace_clock";
468 std::string s = ReadFileIntoString(path);
469
470 size_t start = s.find('[');
471 if (start == std::string::npos)
472 return "";
473
474 size_t end = s.find(']', start);
475 if (end == std::string::npos)
476 return "";
477
478 return s.substr(start + 1, end - start - 1);
479 }
480
AvailableClocks()481 std::set<std::string> FtraceProcfs::AvailableClocks() {
482 std::string path = root_ + "trace_clock";
483 std::string s = ReadFileIntoString(path);
484 std::set<std::string> names;
485
486 size_t start = 0;
487 size_t end = 0;
488
489 for (;;) {
490 end = s.find(' ', start);
491 if (end == std::string::npos)
492 end = s.size();
493 while (end > start && s[end - 1] == '\n')
494 end--;
495 if (start == end)
496 break;
497
498 std::string name = s.substr(start, end - start);
499
500 if (name[0] == '[')
501 name = name.substr(1, name.size() - 2);
502
503 names.insert(name);
504
505 if (end == s.size())
506 break;
507
508 start = end + 1;
509 }
510
511 return names;
512 }
513
ReadBufferPercent()514 uint32_t FtraceProcfs::ReadBufferPercent() {
515 std::string path = root_ + "buffer_percent";
516 std::string raw = ReadFileIntoString(path);
517 std::optional<uint32_t> percent =
518 base::StringToUInt32(base::StripSuffix(raw, "\n"));
519 return percent.has_value() ? *percent : 0;
520 }
521
SetBufferPercent(uint32_t percent)522 bool FtraceProcfs::SetBufferPercent(uint32_t percent) {
523 std::string path = root_ + "buffer_percent";
524 return WriteNumberToFile(path, percent);
525 }
526
WriteNumberToFile(const std::string & path,size_t value)527 bool FtraceProcfs::WriteNumberToFile(const std::string& path, size_t value) {
528 // 2^65 requires 20 digits to write.
529 char buf[21];
530 snprintf(buf, sizeof(buf), "%zu", value);
531 return WriteToFile(path, std::string(buf));
532 }
533
WriteToFile(const std::string & path,const std::string & str)534 bool FtraceProcfs::WriteToFile(const std::string& path,
535 const std::string& str) {
536 return WriteFileInternal(path, str, O_WRONLY);
537 }
538
AppendToFile(const std::string & path,const std::string & str)539 bool FtraceProcfs::AppendToFile(const std::string& path,
540 const std::string& str) {
541 return WriteFileInternal(path, str, O_WRONLY | O_APPEND);
542 }
543
OpenPipeForCpu(size_t cpu)544 base::ScopedFile FtraceProcfs::OpenPipeForCpu(size_t cpu) {
545 std::string path =
546 root_ + "per_cpu/cpu" + std::to_string(cpu) + "/trace_pipe_raw";
547 return base::OpenFile(path, O_RDONLY | O_NONBLOCK);
548 }
549
ReadOneCharFromFile(const std::string & path)550 char FtraceProcfs::ReadOneCharFromFile(const std::string& path) {
551 base::ScopedFile fd = base::OpenFile(path, O_RDONLY);
552 PERFETTO_CHECK(fd);
553 char result = '\0';
554 ssize_t bytes = PERFETTO_EINTR(read(fd.get(), &result, 1));
555 PERFETTO_CHECK(bytes == 1 || bytes == -1);
556 return result;
557 }
558
ClearFile(const std::string & path)559 bool FtraceProcfs::ClearFile(const std::string& path) {
560 base::ScopedFile fd = base::OpenFile(path, O_WRONLY | O_TRUNC);
561 return !!fd;
562 }
563
IsFileWriteable(const std::string & path)564 bool FtraceProcfs::IsFileWriteable(const std::string& path) {
565 return access(path.c_str(), W_OK) == 0;
566 }
567
ReadFileIntoString(const std::string & path) const568 std::string FtraceProcfs::ReadFileIntoString(const std::string& path) const {
569 // You can't seek or stat the procfs files on Android.
570 // The vast majority (884/886) of format files are under 4k.
571 std::string str;
572 str.reserve(4096);
573 if (!base::ReadFile(path, &str))
574 return "";
575 return str;
576 }
577
GetEventNamesForGroup(const std::string & path) const578 const std::set<std::string> FtraceProcfs::GetEventNamesForGroup(
579 const std::string& path) const {
580 std::set<std::string> names;
581 std::string full_path = root_ + path;
582 base::ScopedDir dir(opendir(full_path.c_str()));
583 if (!dir) {
584 PERFETTO_DLOG("Unable to read events from %s", full_path.c_str());
585 return names;
586 }
587 struct dirent* ent;
588 while ((ent = readdir(*dir)) != nullptr) {
589 if (strncmp(ent->d_name, ".", 1) == 0 ||
590 strncmp(ent->d_name, "..", 2) == 0) {
591 continue;
592 }
593 // Check ent is a directory.
594 struct stat statbuf;
595 std::string dir_path = full_path + "/" + ent->d_name;
596 if (stat(dir_path.c_str(), &statbuf) == 0) {
597 if (S_ISDIR(statbuf.st_mode)) {
598 names.insert(ent->d_name);
599 }
600 }
601 }
602 return names;
603 }
604
ReadEventId(const std::string & group,const std::string & name) const605 uint32_t FtraceProcfs::ReadEventId(const std::string& group,
606 const std::string& name) const {
607 std::string path = root_ + "events/" + group + "/" + name + "/id";
608
609 std::string str;
610 if (!base::ReadFile(path, &str))
611 return 0;
612
613 if (str.size() && str[str.size() - 1] == '\n')
614 str.resize(str.size() - 1);
615
616 std::optional<uint32_t> id = base::StringToUInt32(str);
617 if (!id)
618 return 0;
619 return *id;
620 }
621
622 // static
CheckRootPath(const std::string & root)623 bool FtraceProcfs::CheckRootPath(const std::string& root) {
624 base::ScopedFile fd = base::OpenFile(root + "trace", O_RDONLY);
625 return static_cast<bool>(fd);
626 }
627
628 } // namespace perfetto
629