• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <fstream>
25 #include <sstream>
26 #include <string>
27 
28 #include "perfetto/base/logging.h"
29 #include "perfetto/ext/base/file_utils.h"
30 #include "perfetto/ext/base/string_splitter.h"
31 #include "perfetto/ext/base/string_utils.h"
32 #include "perfetto/ext/base/utils.h"
33 
34 namespace perfetto {
35 
36 // Reading /trace produces human readable trace output.
37 // Writing to this file clears all trace buffers for all CPUS.
38 
39 // Writing to /trace_marker file injects an event into the trace buffer.
40 
41 // Reading /tracing_on returns 1/0 if tracing is enabled/disabled.
42 // Writing 1/0 to this file enables/disables tracing.
43 // Disabling tracing with this file prevents further writes but
44 // does not clear the buffer.
45 
46 namespace {
47 
48 namespace {
49 constexpr char kRssStatThrottledTrigger[] =
50     "hist:keys=mm_id,member:bucket=size/0x80000"
51     ":onchange($bucket).rss_stat_throttled(mm_id,curr,member,size)";
52 
53 constexpr char kSuspendResumeMinimalTrigger[] =
54     "hist:keys=start:size=128:onmatch(power.suspend_resume)"
55     ".trace(suspend_resume_minimal, start) if action == 'syscore_resume'";
56 }
57 
KernelLogWrite(const char * s)58 void KernelLogWrite(const char* s) {
59   PERFETTO_DCHECK(*s && s[strlen(s) - 1] == '\n');
60   if (FtraceProcfs::g_kmesg_fd != -1)
61     base::ignore_result(base::WriteAll(FtraceProcfs::g_kmesg_fd, s, strlen(s)));
62 }
63 
WriteFileInternal(const std::string & path,const std::string & str,int flags)64 bool WriteFileInternal(const std::string& path,
65                        const std::string& str,
66                        int flags) {
67   base::ScopedFile fd = base::OpenFile(path, flags);
68   if (!fd)
69     return false;
70   ssize_t written = base::WriteAll(fd.get(), str.c_str(), str.length());
71   ssize_t length = static_cast<ssize_t>(str.length());
72   // This should either fail or write fully.
73   PERFETTO_CHECK(written == length || written == -1);
74   return written == length;
75 }
76 
77 }  // namespace
78 
79 // static
80 int FtraceProcfs::g_kmesg_fd = -1;  // Set by ProbesMain() in probes.cc .
81 
82 const char* const FtraceProcfs::kTracingPaths[] = {
83     "/sys/kernel/tracing/",
84     "/sys/kernel/debug/tracing/",
85     nullptr,
86 };
87 
88 // static
CreateGuessingMountPoint(const std::string & instance_path)89 std::unique_ptr<FtraceProcfs> FtraceProcfs::CreateGuessingMountPoint(
90     const std::string& instance_path) {
91   std::unique_ptr<FtraceProcfs> ftrace_procfs;
92   size_t index = 0;
93   while (!ftrace_procfs && kTracingPaths[index]) {
94     std::string path = kTracingPaths[index++];
95     if (!instance_path.empty())
96       path += instance_path;
97 
98     ftrace_procfs = Create(path);
99   }
100   return ftrace_procfs;
101 }
102 
103 // static
Create(const std::string & root)104 std::unique_ptr<FtraceProcfs> FtraceProcfs::Create(const std::string& root) {
105   if (!CheckRootPath(root))
106     return nullptr;
107   return std::unique_ptr<FtraceProcfs>(new FtraceProcfs(root));
108 }
109 
FtraceProcfs(const std::string & root)110 FtraceProcfs::FtraceProcfs(const std::string& root) : root_(root) {}
111 FtraceProcfs::~FtraceProcfs() = default;
112 
SetSyscallFilter(const std::set<size_t> & filter)113 bool FtraceProcfs::SetSyscallFilter(const std::set<size_t>& filter) {
114   std::vector<std::string> parts;
115   for (size_t id : filter) {
116     base::StackString<16> m("id == %zu", id);
117     parts.push_back(m.ToStdString());
118   }
119 
120   std::string filter_str = "0";
121   if (!parts.empty()) {
122     filter_str = base::Join(parts, " || ");
123   }
124 
125   for (const char* event : {"sys_enter", "sys_exit"}) {
126     std::string path = root_ + "events/raw_syscalls/" + event + "/filter";
127     if (!WriteToFile(path, filter_str)) {
128       PERFETTO_ELOG("Failed to write file: %s", path.c_str());
129       return false;
130     }
131   }
132   return true;
133 }
134 
EnableEvent(const std::string & group,const std::string & name)135 bool FtraceProcfs::EnableEvent(const std::string& group,
136                                const std::string& name) {
137   std::string path = root_ + "events/" + group + "/" + name + "/enable";
138 
139   // Create any required triggers for the ftrace event being enabled.
140   // Some ftrace events (synthetic events) need to set up an event trigger
141   MaybeSetUpEventTriggers(group, name);
142 
143   if (WriteToFile(path, "1"))
144     return true;
145   path = root_ + "set_event";
146   return AppendToFile(path, group + ":" + name);
147 }
148 
DisableEvent(const std::string & group,const std::string & name)149 bool FtraceProcfs::DisableEvent(const std::string& group,
150                                 const std::string& name) {
151   std::string path = root_ + "events/" + group + "/" + name + "/enable";
152 
153   bool ret = WriteToFile(path, "0");
154   if (!ret) {
155     path = root_ + "set_event";
156     ret = AppendToFile(path, "!" + group + ":" + name);
157   }
158 
159   // Remove any associated event triggers after disabling the event
160   MaybeTearDownEventTriggers(group, name);
161 
162   return ret;
163 }
164 
IsEventAccessible(const std::string & group,const std::string & name)165 bool FtraceProcfs::IsEventAccessible(const std::string& group,
166                                      const std::string& name) {
167   std::string path = root_ + "events/" + group + "/" + name + "/enable";
168 
169   return IsFileWriteable(path);
170 }
171 
DisableAllEvents()172 bool FtraceProcfs::DisableAllEvents() {
173   std::string path = root_ + "events/enable";
174   return WriteToFile(path, "0");
175 }
176 
ReadEventFormat(const std::string & group,const std::string & name) const177 std::string FtraceProcfs::ReadEventFormat(const std::string& group,
178                                           const std::string& name) const {
179   std::string path = root_ + "events/" + group + "/" + name + "/format";
180   return ReadFileIntoString(path);
181 }
182 
GetCurrentTracer()183 std::string FtraceProcfs::GetCurrentTracer() {
184   std::string path = root_ + "current_tracer";
185   std::string current_tracer = ReadFileIntoString(path);
186   return base::StripSuffix(current_tracer, "\n");
187 }
188 
SetCurrentTracer(const std::string & tracer)189 bool FtraceProcfs::SetCurrentTracer(const std::string& tracer) {
190   std::string path = root_ + "current_tracer";
191   return WriteToFile(path, tracer);
192 }
193 
ResetCurrentTracer()194 bool FtraceProcfs::ResetCurrentTracer() {
195   return SetCurrentTracer("nop");
196 }
197 
AppendFunctionFilters(const std::vector<std::string> & filters)198 bool FtraceProcfs::AppendFunctionFilters(
199     const std::vector<std::string>& filters) {
200   std::string path = root_ + "set_ftrace_filter";
201   std::string filter = base::Join(filters, "\n");
202 
203   // The same file accepts special actions to perform when a corresponding
204   // kernel function is hit (regardless of active tracer). For example
205   // "__schedule_bug:traceoff" would disable tracing once __schedule_bug is
206   // called.
207   // We disallow these commands as most of them break the isolation of
208   // concurrent ftrace data sources (as the underlying ftrace instance is
209   // shared).
210   if (base::Contains(filter, ':')) {
211     PERFETTO_ELOG("Filter commands are disallowed.");
212     return false;
213   }
214   return AppendToFile(path, filter);
215 }
216 
ClearFunctionFilters()217 bool FtraceProcfs::ClearFunctionFilters() {
218   std::string path = root_ + "set_ftrace_filter";
219   return ClearFile(path);
220 }
221 
AppendFunctionGraphFilters(const std::vector<std::string> & filters)222 bool FtraceProcfs::AppendFunctionGraphFilters(
223     const std::vector<std::string>& filters) {
224   std::string path = root_ + "set_graph_function";
225   std::string filter = base::Join(filters, "\n");
226   return AppendToFile(path, filter);
227 }
228 
ClearFunctionGraphFilters()229 bool FtraceProcfs::ClearFunctionGraphFilters() {
230   std::string path = root_ + "set_graph_function";
231   return ClearFile(path);
232 }
233 
ReadEventTriggers(const std::string & group,const std::string & name) const234 std::vector<std::string> FtraceProcfs::ReadEventTriggers(
235     const std::string& group,
236     const std::string& name) const {
237   std::string path = root_ + "events/" + group + "/" + name + "/trigger";
238   std::string s = ReadFileIntoString(path);
239   std::vector<std::string> triggers;
240 
241   for (base::StringSplitter ss(s, '\n'); ss.Next();) {
242     std::string trigger = ss.cur_token();
243     if (trigger.empty() || trigger[0] == '#')
244       continue;
245 
246     base::StringSplitter ts(trigger, ' ');
247     PERFETTO_CHECK(ts.Next());
248     triggers.push_back(ts.cur_token());
249   }
250 
251   return triggers;
252 }
253 
CreateEventTrigger(const std::string & group,const std::string & name,const std::string & trigger)254 bool FtraceProcfs::CreateEventTrigger(const std::string& group,
255                                       const std::string& name,
256                                       const std::string& trigger) {
257   std::string path = root_ + "events/" + group + "/" + name + "/trigger";
258   return WriteToFile(path, trigger);
259 }
260 
RemoveEventTrigger(const std::string & group,const std::string & name,const std::string & trigger)261 bool FtraceProcfs::RemoveEventTrigger(const std::string& group,
262                                       const std::string& name,
263                                       const std::string& trigger) {
264   std::string path = root_ + "events/" + group + "/" + name + "/trigger";
265   return WriteToFile(path, "!" + trigger);
266 }
267 
RemoveAllEventTriggers(const std::string & group,const std::string & name)268 bool FtraceProcfs::RemoveAllEventTriggers(const std::string& group,
269                                           const std::string& name) {
270   std::vector<std::string> triggers = ReadEventTriggers(group, name);
271 
272   // Remove the triggers in reverse order since a trigger can depend
273   // on another trigger created earlier.
274   for (auto it = triggers.rbegin(); it != triggers.rend(); ++it)
275     if (!RemoveEventTrigger(group, name, *it))
276       return false;
277   return true;
278 }
279 
MaybeSetUpEventTriggers(const std::string & group,const std::string & name)280 bool FtraceProcfs::MaybeSetUpEventTriggers(const std::string& group,
281                                            const std::string& name) {
282   bool ret = true;
283 
284   if (group == "synthetic") {
285     if (name == "rss_stat_throttled") {
286       ret = RemoveAllEventTriggers("kmem", "rss_stat") &&
287             CreateEventTrigger("kmem", "rss_stat", kRssStatThrottledTrigger);
288     } else if (name == "suspend_resume_minimal") {
289       ret = RemoveAllEventTriggers("power", "suspend_resume") &&
290             CreateEventTrigger("power", "suspend_resume", kSuspendResumeMinimalTrigger);
291     }
292   }
293 
294   if (!ret) {
295     PERFETTO_PLOG("Failed to setup event triggers for %s:%s", group.c_str(),
296                   name.c_str());
297   }
298 
299   return ret;
300 }
301 
MaybeTearDownEventTriggers(const std::string & group,const std::string & name)302 bool FtraceProcfs::MaybeTearDownEventTriggers(const std::string& group,
303                                               const std::string& name) {
304   bool ret = true;
305 
306   if (group == "synthetic") {
307     if (name == "rss_stat_throttled") {
308       ret = RemoveAllEventTriggers("kmem", "rss_stat");
309     } else if (name == "suspend_resume_minimal") {
310       ret = RemoveEventTrigger("power", "suspend_resume", 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; cpu < NumberOfCpus(); 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   if (pages * base::kPageSize > 1 * 1024 * 1024 * 1024) {
419     PERFETTO_ELOG("Tried to set the per CPU buffer size to more than 1gb.");
420     return false;
421   }
422   std::string path = root_ + "buffer_size_kb";
423   return WriteNumberToFile(path, pages * (base::kPageSize / 1024ul));
424 }
425 
GetTracingOn()426 bool FtraceProcfs::GetTracingOn() {
427   std::string path = root_ + "tracing_on";
428   char tracing_on = ReadOneCharFromFile(path);
429   if (tracing_on == '\0')
430     PERFETTO_PLOG("Failed to read %s", path.c_str());
431   return tracing_on == '1';
432 }
433 
SetTracingOn(bool on)434 bool FtraceProcfs::SetTracingOn(bool on) {
435   std::string path = root_ + "tracing_on";
436   if (!WriteToFile(path, on ? "1" : "0")) {
437     PERFETTO_PLOG("Failed to write %s", path.c_str());
438     return false;
439   }
440   if (on) {
441     KernelLogWrite("perfetto: enabled ftrace\n");
442     PERFETTO_LOG("enabled ftrace in %s", root_.c_str());
443   } else {
444     KernelLogWrite("perfetto: disabled ftrace\n");
445     PERFETTO_LOG("disabled ftrace in %s", root_.c_str());
446   }
447 
448   return true;
449 }
450 
IsTracingAvailable()451 bool FtraceProcfs::IsTracingAvailable() {
452   std::string current_tracer = GetCurrentTracer();
453 
454   // Ftrace tracing is available if current_tracer == "nop".
455   // events/enable could be 0, 1, X or 0*. 0* means events would be
456   // dynamically enabled so we need to treat as event tracing is in use.
457   // However based on the discussion in asop/2328817, on Android events/enable
458   // is "X" after boot up. To avoid causing more problem, the decision is just
459   // look at current_tracer.
460   // As the discussion in asop/2328817, if GetCurrentTracer failed to
461   // read file and return "", we treat it as tracing is available.
462   return current_tracer == "nop" || current_tracer == "";
463 }
464 
SetClock(const std::string & clock_name)465 bool FtraceProcfs::SetClock(const std::string& clock_name) {
466   std::string path = root_ + "trace_clock";
467   return WriteToFile(path, clock_name);
468 }
469 
GetClock()470 std::string FtraceProcfs::GetClock() {
471   std::string path = root_ + "trace_clock";
472   std::string s = ReadFileIntoString(path);
473 
474   size_t start = s.find('[');
475   if (start == std::string::npos)
476     return "";
477 
478   size_t end = s.find(']', start);
479   if (end == std::string::npos)
480     return "";
481 
482   return s.substr(start + 1, end - start - 1);
483 }
484 
AvailableClocks()485 std::set<std::string> FtraceProcfs::AvailableClocks() {
486   std::string path = root_ + "trace_clock";
487   std::string s = ReadFileIntoString(path);
488   std::set<std::string> names;
489 
490   size_t start = 0;
491   size_t end = 0;
492 
493   for (;;) {
494     end = s.find(' ', start);
495     if (end == std::string::npos)
496       end = s.size();
497     while (end > start && s[end - 1] == '\n')
498       end--;
499     if (start == end)
500       break;
501 
502     std::string name = s.substr(start, end - start);
503 
504     if (name[0] == '[')
505       name = name.substr(1, name.size() - 2);
506 
507     names.insert(name);
508 
509     if (end == s.size())
510       break;
511 
512     start = end + 1;
513   }
514 
515   return names;
516 }
517 
WriteNumberToFile(const std::string & path,size_t value)518 bool FtraceProcfs::WriteNumberToFile(const std::string& path, size_t value) {
519   // 2^65 requires 20 digits to write.
520   char buf[21];
521   snprintf(buf, sizeof(buf), "%zu", value);
522   return WriteToFile(path, std::string(buf));
523 }
524 
WriteToFile(const std::string & path,const std::string & str)525 bool FtraceProcfs::WriteToFile(const std::string& path,
526                                const std::string& str) {
527   return WriteFileInternal(path, str, O_WRONLY);
528 }
529 
AppendToFile(const std::string & path,const std::string & str)530 bool FtraceProcfs::AppendToFile(const std::string& path,
531                                 const std::string& str) {
532   return WriteFileInternal(path, str, O_WRONLY | O_APPEND);
533 }
534 
OpenPipeForCpu(size_t cpu)535 base::ScopedFile FtraceProcfs::OpenPipeForCpu(size_t cpu) {
536   std::string path =
537       root_ + "per_cpu/cpu" + std::to_string(cpu) + "/trace_pipe_raw";
538   return base::OpenFile(path, O_RDONLY | O_NONBLOCK);
539 }
540 
ReadOneCharFromFile(const std::string & path)541 char FtraceProcfs::ReadOneCharFromFile(const std::string& path) {
542   base::ScopedFile fd = base::OpenFile(path, O_RDONLY);
543   PERFETTO_CHECK(fd);
544   char result = '\0';
545   ssize_t bytes = PERFETTO_EINTR(read(fd.get(), &result, 1));
546   PERFETTO_CHECK(bytes == 1 || bytes == -1);
547   return result;
548 }
549 
ClearFile(const std::string & path)550 bool FtraceProcfs::ClearFile(const std::string& path) {
551   base::ScopedFile fd = base::OpenFile(path, O_WRONLY | O_TRUNC);
552   return !!fd;
553 }
554 
IsFileWriteable(const std::string & path)555 bool FtraceProcfs::IsFileWriteable(const std::string& path) {
556   return access(path.c_str(), W_OK) == 0;
557 }
558 
ReadFileIntoString(const std::string & path) const559 std::string FtraceProcfs::ReadFileIntoString(const std::string& path) const {
560   // You can't seek or stat the procfs files on Android.
561   // The vast majority (884/886) of format files are under 4k.
562   std::string str;
563   str.reserve(4096);
564   if (!base::ReadFile(path, &str))
565     return "";
566   return str;
567 }
568 
GetEventNamesForGroup(const std::string & path) const569 const std::set<std::string> FtraceProcfs::GetEventNamesForGroup(
570     const std::string& path) const {
571   std::set<std::string> names;
572   std::string full_path = root_ + path;
573   base::ScopedDir dir(opendir(full_path.c_str()));
574   if (!dir) {
575     PERFETTO_DLOG("Unable to read events from %s", full_path.c_str());
576     return names;
577   }
578   struct dirent* ent;
579   while ((ent = readdir(*dir)) != nullptr) {
580     if (strncmp(ent->d_name, ".", 1) == 0 ||
581         strncmp(ent->d_name, "..", 2) == 0) {
582       continue;
583     }
584     // Check ent is a directory.
585     struct stat statbuf;
586     std::string dir_path = full_path + "/" + ent->d_name;
587     if (stat(dir_path.c_str(), &statbuf) == 0) {
588       if (S_ISDIR(statbuf.st_mode)) {
589         names.insert(ent->d_name);
590       }
591     }
592   }
593   return names;
594 }
595 
ReadEventId(const std::string & group,const std::string & name) const596 uint32_t FtraceProcfs::ReadEventId(const std::string& group,
597                                    const std::string& name) const {
598   std::string path = root_ + "events/" + group + "/" + name + "/id";
599 
600   std::string str;
601   if (!base::ReadFile(path, &str))
602     return 0;
603 
604   if (str.size() && str[str.size() - 1] == '\n')
605     str.resize(str.size() - 1);
606 
607   std::optional<uint32_t> id = base::StringToUInt32(str);
608   if (!id)
609     return 0;
610   return *id;
611 }
612 
613 // static
CheckRootPath(const std::string & root)614 bool FtraceProcfs::CheckRootPath(const std::string& root) {
615   base::ScopedFile fd = base::OpenFile(root + "trace", O_RDONLY);
616   return static_cast<bool>(fd);
617 }
618 
619 }  // namespace perfetto
620