ftrace_reader: Add FtraceConfigMuxer

Move the logic for handling enabling/disabling/adjusting ftrace
settings into a new class: FtraceConfigMuxer.

Ftrace is a bunch of globaly modifiable persistent state.
Given a number of FtraceConfig's we need to find the best union of all
the settings to make eveyone happy while also watching out for anybody
messing with the ftrace settings at the same time as us.

Specifically FtraceConfigMuxer takes in a *requested* FtraceConfig
(|RequestConfig|), makes a best effort attempt to modify the ftrace
debugfs files to honor those settings without interupting other perfetto
traces already in progress or other users of ftrace, then returns an
FtraceConfigId representing that config or zero on failure.

To see which settings we actually managed to set you can call |GetConfig|
and when you are finished with a config you can signal that with
|RemoveConfig|.

Bug: 73049510
Change-Id: I774ced0341c29ba8274c08170abbecc922dacb62
diff --git a/Android.bp b/Android.bp
index c4fa5a6..04e899d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,7 @@
     "src/ftrace_reader/event_info_constants.cc",
     "src/ftrace_reader/format_parser.cc",
     "src/ftrace_reader/ftrace_config.cc",
+    "src/ftrace_reader/ftrace_config_muxer.cc",
     "src/ftrace_reader/ftrace_controller.cc",
     "src/ftrace_reader/ftrace_procfs.cc",
     "src/ftrace_reader/proto_translation_table.cc",
@@ -218,6 +219,7 @@
     "src/ftrace_reader/event_info_constants.cc",
     "src/ftrace_reader/format_parser.cc",
     "src/ftrace_reader/ftrace_config.cc",
+    "src/ftrace_reader/ftrace_config_muxer.cc",
     "src/ftrace_reader/ftrace_controller.cc",
     "src/ftrace_reader/ftrace_procfs.cc",
     "src/ftrace_reader/ftrace_procfs_integrationtest.cc",
@@ -1926,10 +1928,13 @@
     "src/ftrace_reader/format_parser.cc",
     "src/ftrace_reader/format_parser_unittest.cc",
     "src/ftrace_reader/ftrace_config.cc",
+    "src/ftrace_reader/ftrace_config_muxer.cc",
+    "src/ftrace_reader/ftrace_config_muxer_unittest.cc",
     "src/ftrace_reader/ftrace_config_unittest.cc",
     "src/ftrace_reader/ftrace_controller.cc",
     "src/ftrace_reader/ftrace_controller_unittest.cc",
     "src/ftrace_reader/ftrace_procfs.cc",
+    "src/ftrace_reader/ftrace_procfs_unittest.cc",
     "src/ftrace_reader/proto_translation_table.cc",
     "src/ftrace_reader/proto_translation_table_unittest.cc",
     "src/ftrace_reader/test/cpu_reader_support.cc",
diff --git a/include/perfetto/ftrace_reader/ftrace_config.h b/include/perfetto/ftrace_reader/ftrace_config.h
index 201cd8f..27fe0bf 100644
--- a/include/perfetto/ftrace_reader/ftrace_config.h
+++ b/include/perfetto/ftrace_reader/ftrace_config.h
@@ -24,6 +24,9 @@
 
 namespace perfetto {
 
+// 0 is invalid.
+using FtraceConfigId = uint64_t;
+
 // Utility method for the common case where we don't care about atrace events.
 FtraceConfig CreateFtraceConfig(std::set<std::string> names);
 
diff --git a/include/perfetto/ftrace_reader/ftrace_controller.h b/include/perfetto/ftrace_reader/ftrace_controller.h
index 32a0f2d..62e8ae8 100644
--- a/include/perfetto/ftrace_reader/ftrace_controller.h
+++ b/include/perfetto/ftrace_reader/ftrace_controller.h
@@ -49,11 +49,12 @@
 // Method of last resort to reset ftrace state.
 void HardResetFtraceState();
 
-class FtraceController;
-class ProtoTranslationTable;
 class CpuReader;
-class FtraceProcfs;
 class EventFilter;
+class FtraceController;
+class FtraceConfigMuxer;
+class FtraceProcfs;
+class ProtoTranslationTable;
 
 // To consume ftrace data clients implement a |FtraceSink::Delegate| and use it
 // to create a |FtraceSink|. While the FtraceSink lives FtraceController will
@@ -73,6 +74,7 @@
   };
 
   FtraceSink(base::WeakPtr<FtraceController>,
+             FtraceConfigId id,
              FtraceConfig config,
              std::unique_ptr<EventFilter>,
              Delegate*);
@@ -83,6 +85,9 @@
  private:
   friend FtraceController;
 
+  FtraceSink(const FtraceSink&) = delete;
+  FtraceSink& operator=(const FtraceSink&) = delete;
+
   EventFilter* get_event_filter() { return filter_.get(); }
   protozero::MessageHandle<FtraceEventBundle> GetBundleForCpu(size_t cpu) {
     return delegate_->GetBundleForCpu(cpu);
@@ -93,8 +98,10 @@
   }
 
   const std::set<std::string>& enabled_events();
+
   base::WeakPtr<FtraceController> controller_weak_;
-  FtraceConfig config_;
+  const FtraceConfigId id_;
+  const FtraceConfig config_;
   std::unique_ptr<EventFilter> filter_;
   FtraceSink::Delegate* delegate_;
 };
@@ -114,8 +121,9 @@
  protected:
   // Protected for testing.
   FtraceController(std::unique_ptr<FtraceProcfs>,
-                   base::TaskRunner*,
-                   std::unique_ptr<ProtoTranslationTable>);
+                   std::unique_ptr<ProtoTranslationTable>,
+                   std::unique_ptr<FtraceConfigMuxer>,
+                   base::TaskRunner*);
 
   // Called to read data from the staging pipe for the given |cpu| and parse it
   // into the sinks. Protected and virtual for testing.
@@ -143,15 +151,9 @@
   static void UnblockReaders(base::WeakPtr<FtraceController>);
 
   uint32_t GetDrainPeriodMs();
-  uint32_t GetCpuBufferSizeInPages();
 
   void Register(FtraceSink*);
   void Unregister(FtraceSink*);
-  void RegisterForEvent(const std::string& event_name);
-  void UnregisterForEvent(const std::string& event_name);
-
-  void StartAtrace(const FtraceConfig&);
-  void StopAtrace();
 
   void StartIfNeeded();
   void StopIfNeeded();
@@ -164,11 +166,11 @@
   // End lock-protected members.
 
   std::unique_ptr<FtraceProcfs> ftrace_procfs_;
+  std::unique_ptr<ProtoTranslationTable> table_;
+  std::unique_ptr<FtraceConfigMuxer> ftrace_config_muxer_;
   size_t generation_ = 0;
   bool atrace_running_ = false;
   base::TaskRunner* task_runner_ = nullptr;
-  std::vector<size_t> enabled_count_;
-  std::unique_ptr<ProtoTranslationTable> table_;
   std::map<size_t, std::unique_ptr<CpuReader>> readers_;
   std::set<FtraceSink*> sinks_;
   base::WeakPtrFactory<FtraceController> weak_factory_;
diff --git a/src/ftrace_reader/BUILD.gn b/src/ftrace_reader/BUILD.gn
index feaea22..5fffdb5 100644
--- a/src/ftrace_reader/BUILD.gn
+++ b/src/ftrace_reader/BUILD.gn
@@ -56,8 +56,10 @@
     "cpu_reader_unittest.cc",
     "event_info_unittest.cc",
     "format_parser_unittest.cc",
+    "ftrace_config_muxer_unittest.cc",
     "ftrace_config_unittest.cc",
     "ftrace_controller_unittest.cc",
+    "ftrace_procfs_unittest.cc",
     "proto_translation_table_unittest.cc",
   ]
 }
@@ -118,6 +120,8 @@
     "event_info_constants.h",
     "format_parser.cc",
     "ftrace_config.cc",
+    "ftrace_config_muxer.cc",
+    "ftrace_config_muxer.h",
     "ftrace_controller.cc",
     "ftrace_procfs.cc",
     "ftrace_procfs.h",
diff --git a/src/ftrace_reader/ftrace_config_muxer.cc b/src/ftrace_reader/ftrace_config_muxer.cc
new file mode 100644
index 0000000..26d74a7
--- /dev/null
+++ b/src/ftrace_reader/ftrace_config_muxer.cc
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ftrace_config_muxer.h"
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "perfetto/base/utils.h"
+#include "proto_translation_table.h"
+
+namespace perfetto {
+namespace {
+
+// trace_clocks in preference order.
+const char* kClocks[] = {"boot", "global", "local"};
+
+const int kDefaultPerCpuBufferSizeKb = 512;   // 512kb
+const int kMaxPerCpuBufferSizeKb = 2 * 1024;  // 2mb
+
+std::vector<std::string> difference(const std::set<std::string>& a,
+                                    const std::set<std::string>& b) {
+  std::vector<std::string> result;
+  result.reserve(std::max(b.size(), a.size()));
+  std::set_difference(a.begin(), a.end(), b.begin(), b.end(),
+                      std::inserter(result, result.begin()));
+  return result;
+}
+
+// Including "atrace" for argv[0].
+bool RunAtrace(const std::vector<std::string>& args) {
+  int status = 1;
+
+  std::vector<char*> argv;
+  // args, and then a null.
+  argv.reserve(1 + args.size());
+  for (const auto& arg : args)
+    argv.push_back(const_cast<char*>(arg.c_str()));
+  argv.push_back(nullptr);
+
+  pid_t pid = fork();
+  PERFETTO_CHECK(pid >= 0);
+  if (pid == 0) {
+    execv("/system/bin/atrace", &argv[0]);
+    // Reached only if execv fails.
+    _exit(1);
+  }
+  PERFETTO_EINTR(waitpid(pid, &status, 0));
+  return status == 0;
+}
+
+}  // namespace
+
+std::set<std::string> GetFtraceEvents(const FtraceConfig& request) {
+  std::set<std::string> events;
+  events.insert(request.event_names().begin(), request.event_names().end());
+  if (RequiresAtrace(request)) {
+    events.insert("print");
+  }
+  return events;
+}
+
+// Post-conditions:
+// 1. result >= 1 (should have at least one page per CPU)
+// 2. result * 4 < kMaxTotalBufferSizeKb
+// 3. If input is 0 output is a good default number.
+size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb) {
+  if (requested_buffer_size_kb == 0)
+    requested_buffer_size_kb = kDefaultPerCpuBufferSizeKb;
+  if (requested_buffer_size_kb > kMaxPerCpuBufferSizeKb)
+    requested_buffer_size_kb = kDefaultPerCpuBufferSizeKb;
+
+  size_t pages = requested_buffer_size_kb / (base::kPageSize / 1024);
+  if (pages == 0)
+    return 1;
+
+  return pages;
+}
+
+FtraceConfigMuxer::FtraceConfigMuxer(FtraceProcfs* ftrace,
+                                     const ProtoTranslationTable* table)
+    : ftrace_(ftrace), table_(table), current_state_(), configs_() {}
+FtraceConfigMuxer::~FtraceConfigMuxer() = default;
+
+FtraceConfigId FtraceConfigMuxer::RequestConfig(const FtraceConfig& request) {
+  FtraceConfig actual;
+
+  bool is_ftrace_enabled = ftrace_->IsTracingEnabled();
+  if (configs_.empty()) {
+    PERFETTO_DCHECK(!current_state_.tracing_on);
+
+    // If someone outside of perfetto is using ftrace give up now.
+    if (is_ftrace_enabled)
+      return 0;
+
+    // If we're about to turn tracing on use this opportunity do some setup:
+    if (RequiresAtrace(request))
+      EnableAtraceOnAndroid(request);
+    SetupClock(request);
+    SetupBufferSize(request);
+  } else {
+    // Did someone turn ftrace off behind our back? If so give up.
+    if (!is_ftrace_enabled)
+      return 0;
+  }
+
+  std::set<std::string> events = GetFtraceEvents(request);
+
+  for (auto& name : events) {
+    const Event* event = table_->GetEventByName(name);
+    if (!event) {
+      PERFETTO_DLOG("Can't enable %s, event not known", name.c_str());
+      continue;
+    }
+    if (current_state_.ftrace_events.count(name) ||
+        std::string("ftrace") == event->group) {
+      *actual.add_event_names() = name;
+      continue;
+    }
+    if (ftrace_->EnableEvent(event->group, event->name)) {
+      current_state_.ftrace_events.insert(name);
+      *actual.add_event_names() = name;
+    }
+  }
+
+  if (configs_.empty()) {
+    PERFETTO_DCHECK(!current_state_.tracing_on);
+    ftrace_->EnableTracing();
+    current_state_.tracing_on = true;
+  }
+
+  FtraceConfigId id = ++last_id_;
+  configs_.emplace(id, std::move(actual));
+  return id;
+}
+
+bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId id) {
+  if (!id || !configs_.erase(id))
+    return false;
+
+  std::set<std::string> expected_ftrace_events;
+  for (const auto& id_config : configs_) {
+    const FtraceConfig& config = id_config.second;
+    expected_ftrace_events.insert(config.event_names().begin(),
+                                  config.event_names().end());
+  }
+
+  std::vector<std::string> events_to_disable =
+      difference(current_state_.ftrace_events, expected_ftrace_events);
+
+  for (auto& name : events_to_disable) {
+    const Event* event = table_->GetEventByName(name);
+    if (!event)
+      continue;
+    if (ftrace_->DisableEvent(event->group, event->name))
+      current_state_.ftrace_events.erase(name);
+  }
+
+  if (configs_.empty()) {
+    PERFETTO_DCHECK(current_state_.tracing_on);
+    ftrace_->DisableTracing();
+    ftrace_->SetCpuBufferSizeInPages(0);
+    ftrace_->DisableAllEvents();
+    ftrace_->ClearTrace();
+    current_state_.tracing_on = false;
+    if (current_state_.atrace_on)
+      DisableAtraceOnAndroid();
+  }
+
+  return true;
+}
+
+const FtraceConfig* FtraceConfigMuxer::GetConfig(FtraceConfigId id) {
+  if (!configs_.count(id))
+    return nullptr;
+  return &configs_.at(id);
+}
+
+void FtraceConfigMuxer::SetupClock(const FtraceConfig&) {
+  std::string current_clock = ftrace_->GetClock();
+  std::set<std::string> clocks = ftrace_->AvailableClocks();
+
+  for (size_t i = 0; i < base::ArraySize(kClocks); i++) {
+    std::string clock = std::string(kClocks[i]);
+    if (!clocks.count(clock))
+      continue;
+    if (current_clock == clock)
+      break;
+    ftrace_->SetClock(clock);
+    break;
+  }
+}
+
+void FtraceConfigMuxer::SetupBufferSize(const FtraceConfig& request) {
+  size_t pages = ComputeCpuBufferSizeInPages(request.buffer_size_kb());
+  ftrace_->SetCpuBufferSizeInPages(pages);
+  current_state_.cpu_buffer_size_pages = pages;
+}
+
+void FtraceConfigMuxer::EnableAtraceOnAndroid(const FtraceConfig& request) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  EnableAtrace(request);
+#else
+  PERFETTO_LOG("Atrace only supported on Android.");
+#endif
+}
+
+void FtraceConfigMuxer::EnableAtrace(const FtraceConfig& request) {
+  PERFETTO_DCHECK(!current_state_.atrace_on);
+  current_state_.atrace_on = true;
+
+  PERFETTO_DLOG("Start atrace...");
+  std::vector<std::string> args;
+  args.push_back("atrace");  // argv0 for exec()
+  args.push_back("--async_start");
+  for (const auto& category : request.atrace_categories())
+    args.push_back(category);
+  if (!request.atrace_apps().empty()) {
+    args.push_back("-a");
+    for (const auto& app : request.atrace_apps())
+      args.push_back(app);
+  }
+
+  PERFETTO_CHECK(RunAtrace(args));
+  PERFETTO_DLOG("...done");
+}
+
+void FtraceConfigMuxer::DisableAtraceOnAndroid() {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  DisableAtrace();
+#else
+  PERFETTO_LOG("Atrace only supported on Android.");
+#endif
+}
+
+void FtraceConfigMuxer::DisableAtrace() {
+  PERFETTO_DCHECK(!current_state_.atrace_on);
+
+  PERFETTO_DLOG("Stop atrace...");
+  PERFETTO_CHECK(RunAtrace({"atrace", "--async_stop"}));
+  PERFETTO_DLOG("...done");
+
+  current_state_.atrace_on = false;
+}
+
+}  // namespace perfetto
diff --git a/src/ftrace_reader/ftrace_config_muxer.h b/src/ftrace_reader/ftrace_config_muxer.h
new file mode 100644
index 0000000..37d031b
--- /dev/null
+++ b/src/ftrace_reader/ftrace_config_muxer.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_FTRACE_READER_FTRACE_CONFIG_MUXER_H_
+#define SRC_FTRACE_READER_FTRACE_CONFIG_MUXER_H_
+
+#include "ftrace_procfs.h"
+#include "perfetto/ftrace_reader/ftrace_controller.h"
+
+namespace perfetto {
+
+// Ftrace is a bunch of globaly modifiable persistent state.
+// Given a number of FtraceConfig's we need to find the best union of all
+// the settings to make eveyone happy while also watching out for anybody
+// messing with the ftrace settings at the same time as us.
+
+// Specifically FtraceConfigMuxer takes in a *requested* FtraceConfig
+// (|RequestConfig|), makes a best effort attempt to modify the ftrace
+// debugfs files to honor those settings without interupting other perfetto
+// traces already in progress or other users of ftrace, then returns an
+// FtraceConfigId representing that config or zero on failure.
+
+// To see which settings we actually managed to set you can call |GetConfig|
+// and when you are finished with a config you can signal that with
+// |RemoveConfig|.
+class FtraceConfigMuxer {
+ public:
+  // The ProtoTranslationTable table should outlive this instance.
+  FtraceConfigMuxer(FtraceProcfs* ftrace, const ProtoTranslationTable* table);
+  virtual ~FtraceConfigMuxer();
+
+  // Ask FtraceConfigMuxer to adjust ftrace procfs settings to
+  // match the requested config. Returns an id to manage this
+  // config or zero on failure.
+  // This is best effort. FtraceConfigMuxer may not be able to adjust the
+  // buffer size right now. Events may be missing or there may be extra events
+  // (if you enable an atrace catagory we try to give you the matching events).
+  // If someone else is tracing we won't touch atrace (since it resets the
+  // buffer).
+  // To see the config you ended up with use |GetConfig|.
+  FtraceConfigId RequestConfig(const FtraceConfig& request);
+
+  // Undo changes for the given config. Returns false iff the id is 0
+  // or already removed.
+  bool RemoveConfig(FtraceConfigId id);
+
+  // public for testing
+  void SetupClockForTesting(const FtraceConfig& request) {
+    SetupClock(request);
+  }
+
+  const FtraceConfig* GetConfig(FtraceConfigId id);
+
+ private:
+  struct FtraceState {
+    std::set<std::string> ftrace_events;
+    std::set<std::string> atrace_categories;
+    std::set<std::string> atrace_apps;
+    bool tracing_on = false;
+    bool atrace_on = false;
+    size_t cpu_buffer_size_pages = 0;
+  };
+
+  FtraceConfigMuxer(const FtraceConfigMuxer&) = delete;
+  FtraceConfigMuxer& operator=(const FtraceConfigMuxer&) = delete;
+
+  void SetupClock(const FtraceConfig& request);
+  void SetupBufferSize(const FtraceConfig& request);
+  void EnableAtraceOnAndroid(const FtraceConfig& request);
+  void DisableAtraceOnAndroid();
+  void EnableAtrace(const FtraceConfig& request);
+  void DisableAtrace();
+
+  FtraceConfigId GetNextId();
+
+  FtraceConfigId last_id_ = 1;
+  FtraceProcfs* ftrace_;
+  const ProtoTranslationTable* table_;
+
+  FtraceState current_state_;
+  std::map<FtraceConfigId, FtraceConfig> configs_;
+};
+
+std::set<std::string> GetFtraceEvents(const FtraceConfig& request);
+size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb);
+
+}  // namespace perfetto
+
+#endif  // SRC_FTRACE_READER_FTRACE_CONFIG_MUXER_H_
diff --git a/src/ftrace_reader/ftrace_config_muxer_unittest.cc b/src/ftrace_reader/ftrace_config_muxer_unittest.cc
new file mode 100644
index 0000000..d09bd88
--- /dev/null
+++ b/src/ftrace_reader/ftrace_config_muxer_unittest.cc
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ftrace_config_muxer.h"
+
+#include <memory>
+
+#include "ftrace_procfs.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "proto_translation_table.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::Contains;
+using testing::IsEmpty;
+using testing::NiceMock;
+using testing::Not;
+using testing::Return;
+using testing::UnorderedElementsAre;
+
+namespace perfetto {
+namespace {
+
+class MockFtraceProcfs : public FtraceProcfs {
+ public:
+  MockFtraceProcfs() : FtraceProcfs("/root/") {
+    ON_CALL(*this, NumberOfCpus()).WillByDefault(Return(1));
+    ON_CALL(*this, WriteToFile(_, _)).WillByDefault(Return(true));
+    ON_CALL(*this, ClearFile(_)).WillByDefault(Return(true));
+    EXPECT_CALL(*this, NumberOfCpus()).Times(AnyNumber());
+  }
+
+  MOCK_METHOD2(WriteToFile,
+               bool(const std::string& path, const std::string& str));
+  MOCK_METHOD1(ReadOneCharFromFile, char(const std::string& path));
+  MOCK_METHOD1(ClearFile, bool(const std::string& path));
+  MOCK_CONST_METHOD1(ReadFileIntoString, std::string(const std::string& path));
+  MOCK_CONST_METHOD0(NumberOfCpus, size_t());
+};
+
+std::unique_ptr<ProtoTranslationTable> CreateFakeTable() {
+  std::vector<Field> common_fields;
+  std::vector<Event> events;
+
+  {
+    Event event;
+    event.name = "sched_switch";
+    event.group = "sched";
+    event.ftrace_event_id = 1;
+    events.push_back(event);
+  }
+
+  {
+    Event event;
+    event.name = "sched_wakeup";
+    event.group = "sched";
+    event.ftrace_event_id = 10;
+    events.push_back(event);
+  }
+
+  {
+    Event event;
+    event.name = "sched_new";
+    event.group = "sched";
+    event.ftrace_event_id = 20;
+    events.push_back(event);
+  }
+
+  {
+    Event event;
+    event.name = "print";
+    event.group = "ftrace";
+    event.ftrace_event_id = 20;
+    events.push_back(event);
+  }
+
+  return std::unique_ptr<ProtoTranslationTable>(
+      new ProtoTranslationTable(events, std::move(common_fields)));
+}
+
+TEST(FtraceConfigMuxer, ComputeCpuBufferSizeInPages) {
+  // No buffer size given: good default (128 pages = 512kb).
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(0), 128u);
+  // Buffer size given way too big: good default.
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(10 * 1024 * 1024), 128u);
+  // The limit is 2mb per CPU, 3mb is too much.
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(3 * 1024), 128u);
+  // Your size ends up with less than 1 page per cpu -> 1 page.
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(3), 1u);
+  // You picked a good size -> your size rounded to nearest page.
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(42), 10u);
+}
+
+TEST(FtraceConfigMuxerTest, TurnFtraceOnOff) {
+  std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable();
+  MockFtraceProcfs ftrace;
+
+  FtraceConfig config = CreateFtraceConfig({"sched_switch", "foo"});
+
+  FtraceConfigMuxer model(&ftrace, table.get());
+
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return("[local] global boot"));
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .Times(AnyNumber());
+
+  EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
+      .WillOnce(Return('0'));
+  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "512"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "1"));
+  EXPECT_CALL(ftrace,
+              WriteToFile("/root/events/sched/sched_switch/enable", "1"));
+  FtraceConfigId id = model.RequestConfig(config);
+  ASSERT_TRUE(id);
+
+  const FtraceConfig* actual_config = model.GetConfig(id);
+  EXPECT_TRUE(actual_config);
+  EXPECT_THAT(actual_config->event_names(), Contains("sched_switch"));
+  EXPECT_THAT(actual_config->event_names(), Not(Contains("foo")));
+
+  EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "0"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/events/enable", "0"));
+  EXPECT_CALL(ftrace,
+              WriteToFile("/root/events/sched/sched_switch/enable", "0"));
+  EXPECT_CALL(ftrace, ClearFile("/root/trace"));
+  ASSERT_TRUE(model.RemoveConfig(id));
+}
+
+TEST(FtraceConfigMuxerTest, FtraceIsAlreadyOn) {
+  std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable();
+  MockFtraceProcfs ftrace;
+
+  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
+
+  FtraceConfigMuxer model(&ftrace, table.get());
+
+  // If someone is using ftrace already don't stomp on what they are doing.
+  EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
+      .WillOnce(Return('1'));
+  FtraceConfigId id = model.RequestConfig(config);
+  ASSERT_FALSE(id);
+}
+
+// TODO(hjd): Mock atrace on Android.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#define MAYBE_Atrace DISABLED_Atrace
+#else
+#define MAYBE_Atrace Atrace
+#endif
+TEST(FtraceConfigMuxerTest, MAYBE_Atrace) {
+  std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable();
+  NiceMock<MockFtraceProcfs> ftrace;
+
+  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
+  *config.add_atrace_categories() = "sched";
+
+  FtraceConfigMuxer model(&ftrace, table.get());
+
+  EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
+      .WillOnce(Return('0'));
+
+  FtraceConfigId id = model.RequestConfig(config);
+  ASSERT_TRUE(id);
+
+  const FtraceConfig* actual_config = model.GetConfig(id);
+  EXPECT_TRUE(actual_config);
+  EXPECT_THAT(actual_config->event_names(), Contains("sched_switch"));
+  EXPECT_THAT(actual_config->event_names(), Contains("print"));
+
+  ASSERT_TRUE(model.RemoveConfig(id));
+}
+
+TEST(FtraceConfigMuxerTest, SetupClockForTesting) {
+  std::unique_ptr<ProtoTranslationTable> table = CreateFakeTable();
+  MockFtraceProcfs ftrace;
+  FtraceConfig config;
+
+  FtraceConfigMuxer model(&ftrace, table.get());
+
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .Times(AnyNumber());
+
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return("[local] global boot"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "boot"));
+  model.SetupClockForTesting(config);
+
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return("[local] global"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/trace_clock", "global"));
+  model.SetupClockForTesting(config);
+
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return(""));
+  model.SetupClockForTesting(config);
+
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return("local [global]"));
+  model.SetupClockForTesting(config);
+}
+
+TEST(FtraceConfigMuxerTest, GetFtraceEvents) {
+  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
+  std::set<std::string> events = GetFtraceEvents(config);
+
+  EXPECT_THAT(events, Contains("sched_switch"));
+  EXPECT_THAT(events, Not(Contains("print")));
+}
+
+TEST(FtraceConfigMuxerTest, GetFtraceEventsAtrace) {
+  FtraceConfig config = CreateFtraceConfig({"sched_switch"});
+  *config.add_atrace_categories() = "sched";
+  std::set<std::string> events = GetFtraceEvents(config);
+
+  EXPECT_THAT(events, Contains("sched_switch"));
+  EXPECT_THAT(events, Contains("print"));
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/ftrace_reader/ftrace_controller.cc b/src/ftrace_reader/ftrace_controller.cc
index dd25c3e..b0e880c 100644
--- a/src/ftrace_reader/ftrace_controller.cc
+++ b/src/ftrace_reader/ftrace_controller.cc
@@ -29,6 +29,7 @@
 
 #include "cpu_reader.h"
 #include "event_info.h"
+#include "ftrace_config_muxer.h"
 #include "ftrace_procfs.h"
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
@@ -54,9 +55,6 @@
 const int kMinDrainPeriodMs = 1;
 const int kMaxDrainPeriodMs = 1000 * 60;
 
-const int kDefaultTotalBufferSizeKb = 1024 * 4;  // 4mb
-const int kMaxTotalBufferSizeKb = 1024 * 8;      // 8mb
-
 uint32_t ClampDrainPeriodMs(uint32_t drain_period_ms) {
   if (drain_period_ms == 0) {
     return kDefaultDrainPeriodMs;
@@ -70,44 +68,6 @@
   return drain_period_ms;
 }
 
-// Post-conditions:
-// 1. result >= 1 (should have at least one page per CPU)
-// 2. result * 4 < kMaxTotalBufferSizeKb
-// 3. If input is 0 output is a good default number.
-size_t ComputeCpuBufferSizeInPages(uint32_t requested_buffer_size_kb) {
-  if (requested_buffer_size_kb == 0)
-    requested_buffer_size_kb = kDefaultTotalBufferSizeKb;
-  if (requested_buffer_size_kb > kMaxTotalBufferSizeKb)
-    requested_buffer_size_kb = kDefaultTotalBufferSizeKb;
-
-  size_t pages = requested_buffer_size_kb / (base::kPageSize / 1024);
-  if (pages == 0)
-    return 1;
-
-  return pages;
-}
-
-bool RunAtrace(std::vector<std::string> args) {
-  int status = 1;
-
-  std::vector<char*> argv;
-  // args, and then a null.
-  argv.reserve(1 + args.size());
-  for (const auto& arg : args)
-    argv.push_back(const_cast<char*>(arg.c_str()));
-  argv.push_back(nullptr);
-
-  pid_t pid = fork();
-  PERFETTO_CHECK(pid >= 0);
-  if (pid == 0) {
-    execv("/system/bin/atrace", &argv[0]);
-    // Reached only if execv fails.
-    _exit(1);
-  }
-  waitpid(pid, &status, 0);
-  return status == 0;
-}
-
 void WriteToFile(const char* path, const char* str) {
   int fd = open(path, O_WRONLY);
   if (fd == -1)
@@ -151,33 +111,32 @@
     ftrace_procfs = FtraceProcfs::Create(kTracingPaths[index++]);
   }
 
-  if (!ftrace_procfs) {
+  if (!ftrace_procfs)
     return nullptr;
-  }
 
   auto table = ProtoTranslationTable::Create(
       ftrace_procfs.get(), GetStaticEventInfo(), GetStaticCommonFieldsInfo());
-  return std::unique_ptr<FtraceController>(
-      new FtraceController(std::move(ftrace_procfs), runner, std::move(table)));
+
+  std::unique_ptr<FtraceConfigMuxer> model = std::unique_ptr<FtraceConfigMuxer>(
+      new FtraceConfigMuxer(ftrace_procfs.get(), table.get()));
+  return std::unique_ptr<FtraceController>(new FtraceController(
+      std::move(ftrace_procfs), std::move(table), std::move(model), runner));
 }
 
 FtraceController::FtraceController(std::unique_ptr<FtraceProcfs> ftrace_procfs,
-                                   base::TaskRunner* task_runner,
-                                   std::unique_ptr<ProtoTranslationTable> table)
+                                   std::unique_ptr<ProtoTranslationTable> table,
+                                   std::unique_ptr<FtraceConfigMuxer> model,
+                                   base::TaskRunner* task_runner)
     : ftrace_procfs_(std::move(ftrace_procfs)),
-      task_runner_(task_runner),
-      enabled_count_(table->largest_id() + 1),
       table_(std::move(table)),
+      ftrace_config_muxer_(std::move(model)),
+      task_runner_(task_runner),
       weak_factory_(this) {}
 
 FtraceController::~FtraceController() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
-  for (size_t id = 1; id <= table_->largest_id(); id++) {
-    if (enabled_count_[id]) {
-      const Event* event = table_->GetEventById(id);
-      ftrace_procfs_->DisableEvent(event->group, event->name);
-    }
-  }
+  for (const auto* sink : sinks_)
+    ftrace_config_muxer_->RemoveConfig(sink->id_);
   sinks_.clear();
   StopIfNeeded();
 }
@@ -242,8 +201,6 @@
     PERFETTO_CHECK(!listening_for_raw_trace_data_);
     listening_for_raw_trace_data_ = true;
   }
-  ftrace_procfs_->SetCpuBufferSizeInPages(GetCpuBufferSizeInPages());
-  ftrace_procfs_->EnableTracing();
   generation_++;
   base::WeakPtr<FtraceController> weak_this = weak_factory_.GetWeakPtr();
   for (size_t cpu = 0; cpu < ftrace_procfs_->NumberOfCpus(); cpu++) {
@@ -266,15 +223,6 @@
   return ClampDrainPeriodMs(min_drain_period_ms);
 }
 
-uint32_t FtraceController::GetCpuBufferSizeInPages() {
-  uint32_t max_buffer_size_kb = 0;
-  for (const FtraceSink* sink : sinks_) {
-    if (sink->config().buffer_size_kb() > max_buffer_size_kb)
-      max_buffer_size_kb = sink->config().buffer_size_kb();
-  }
-  return ComputeCpuBufferSizeInPages(max_buffer_size_kb);
-}
-
 void FtraceController::ClearTrace() {
   ftrace_procfs_->ClearTrace();
 }
@@ -293,8 +241,6 @@
   {
     // Unblock any readers that are waiting for us to drain data.
     std::unique_lock<std::mutex> lock(lock_);
-    if (listening_for_raw_trace_data_)
-      ftrace_procfs_->DisableTracing();
     listening_for_raw_trace_data_ = false;
     cpus_to_drain_.reset();
   }
@@ -330,13 +276,17 @@
     return nullptr;
   if (!ValidConfig(config))
     return nullptr;
+
+  FtraceConfigId id = ftrace_config_muxer_->RequestConfig(config);
+  if (!id)
+    return nullptr;
+
   auto controller_weak = weak_factory_.GetWeakPtr();
-  auto filter = std::unique_ptr<EventFilter>(
-      new EventFilter(*table_.get(), FtraceEventsAsSet(config)));
-  for (const std::string& event : config.event_names())
-    PERFETTO_LOG("%s", event.c_str());
+  auto filter = std::unique_ptr<EventFilter>(new EventFilter(
+      *table_.get(), FtraceEventsAsSet(*ftrace_config_muxer_->GetConfig(id))));
+
   auto sink = std::unique_ptr<FtraceSink>(new FtraceSink(
-      std::move(controller_weak), config, std::move(filter), delegate));
+      std::move(controller_weak), id, config, std::move(filter), delegate));
   Register(sink.get());
   return sink;
 }
@@ -375,83 +325,27 @@
   PERFETTO_DCHECK_THREAD(thread_checker_);
   auto it_and_inserted = sinks_.insert(sink);
   PERFETTO_DCHECK(it_and_inserted.second);
-  if (RequiresAtrace(sink->config()))
-    StartAtrace(sink->config());
-
   StartIfNeeded();
-  for (const std::string& name : sink->enabled_events())
-    RegisterForEvent(name);
-}
-
-void FtraceController::RegisterForEvent(const std::string& name) {
-  PERFETTO_DCHECK_THREAD(thread_checker_);
-  const Event* event = table_->GetEventByName(name);
-  if (!event) {
-    PERFETTO_DLOG("Can't enable %s, event not known", name.c_str());
-    return;
-  }
-  size_t& count = enabled_count_.at(event->ftrace_event_id);
-  if (count == 0)
-    ftrace_procfs_->EnableEvent(event->group, event->name);
-  count += 1;
-}
-
-void FtraceController::UnregisterForEvent(const std::string& name) {
-  PERFETTO_DCHECK_THREAD(thread_checker_);
-  const Event* event = table_->GetEventByName(name);
-  if (!event)
-    return;
-  size_t& count = enabled_count_.at(event->ftrace_event_id);
-  PERFETTO_CHECK(count > 0);
-  if (--count == 0)
-    ftrace_procfs_->DisableEvent(event->group, event->name);
 }
 
 void FtraceController::Unregister(FtraceSink* sink) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
+
   size_t removed = sinks_.erase(sink);
   PERFETTO_DCHECK(removed == 1);
 
-  for (const std::string& name : sink->enabled_events())
-    UnregisterForEvent(name);
-  if (RequiresAtrace(sink->config()))
-    StopAtrace();
+  ftrace_config_muxer_->RemoveConfig(sink->id_);
+
   StopIfNeeded();
 }
 
-void FtraceController::StartAtrace(const FtraceConfig& config) {
-  PERFETTO_CHECK(atrace_running_ == false);
-  atrace_running_ = true;
-  PERFETTO_DLOG("Start atrace...");
-  std::vector<std::string> args;
-  args.push_back("atrace");  // argv0 for exec()
-  args.push_back("--async_start");
-  for (const auto& category : config.atrace_categories())
-    args.push_back(category);
-  if (!config.atrace_apps().empty()) {
-    args.push_back("-a");
-    for (const auto& app : config.atrace_apps())
-      args.push_back(app);
-  }
-
-  PERFETTO_CHECK(RunAtrace(std::move(args)));
-  PERFETTO_DLOG("...done");
-}
-
-void FtraceController::StopAtrace() {
-  PERFETTO_CHECK(atrace_running_ == true);
-  atrace_running_ = false;
-  PERFETTO_DLOG("Stop atrace...");
-  PERFETTO_CHECK(
-      RunAtrace(std::vector<std::string>({"atrace", "--async_stop"})));
-  PERFETTO_DLOG("...done");
-}
-
 FtraceSink::FtraceSink(base::WeakPtr<FtraceController> controller_weak,
+                       FtraceConfigId id,
                        FtraceConfig config,
                        std::unique_ptr<EventFilter> filter,
                        Delegate* delegate)
     : controller_weak_(std::move(controller_weak)),
+      id_(id),
       config_(config),
       filter_(std::move(filter)),
       delegate_(delegate){};
diff --git a/src/ftrace_reader/ftrace_controller_unittest.cc b/src/ftrace_reader/ftrace_controller_unittest.cc
index bd27370..4526547 100644
--- a/src/ftrace_reader/ftrace_controller_unittest.cc
+++ b/src/ftrace_reader/ftrace_controller_unittest.cc
@@ -21,15 +21,14 @@
 #include <sys/types.h>
 
 #include "cpu_reader.h"
+#include "ftrace_config_muxer.h"
 #include "ftrace_procfs.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
 #include "perfetto/ftrace_reader/ftrace_config.h"
+#include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "proto_translation_table.h"
 
-#include "src/base/test/test_task_runner.h"
-
-#include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
 
 using testing::_;
 using testing::AnyNumber;
@@ -122,11 +121,43 @@
   return std::unique_ptr<Table>(new Table(events, std::move(common_fields)));
 }
 
+std::unique_ptr<FtraceConfigMuxer> FakeModel(
+    FtraceProcfs* ftrace,
+    const ProtoTranslationTable* table) {
+  return std::unique_ptr<FtraceConfigMuxer>(
+      new FtraceConfigMuxer(ftrace, table));
+}
+
 class MockFtraceProcfs : public FtraceProcfs {
  public:
   MockFtraceProcfs(size_t cpu_count = 1) : FtraceProcfs("/root/") {
     ON_CALL(*this, NumberOfCpus()).WillByDefault(Return(cpu_count));
     EXPECT_CALL(*this, NumberOfCpus()).Times(AnyNumber());
+
+    ON_CALL(*this, ReadFileIntoString("/root/trace_clock"))
+        .WillByDefault(Return("local global [boot]"));
+    EXPECT_CALL(*this, ReadFileIntoString("/root/trace_clock"))
+        .Times(AnyNumber());
+
+    ON_CALL(*this, WriteToFile(_, _)).WillByDefault(Return(true));
+    ON_CALL(*this, ClearFile(_)).WillByDefault(Return(true));
+
+    ON_CALL(*this, WriteToFile("/root/tracing_on", _))
+        .WillByDefault(Invoke(this, &MockFtraceProcfs::WriteTracingOn));
+    ON_CALL(*this, ReadOneCharFromFile("/root/tracing_on"))
+        .WillByDefault(Invoke(this, &MockFtraceProcfs::ReadTracingOn));
+    EXPECT_CALL(*this, ReadOneCharFromFile("/root/tracing_on"))
+        .Times(AnyNumber());
+  }
+
+  bool WriteTracingOn(const std::string& path, const std::string& value) {
+    PERFETTO_CHECK(value == "1" || value == "0");
+    tracing_on_ = value == "1";
+    return true;
+  }
+
+  char ReadTracingOn(const std::string& path) {
+    return tracing_on_ ? '1' : '0';
   }
 
   base::ScopedFile OpenPipeForCpu(size_t cpu) override {
@@ -136,6 +167,14 @@
   MOCK_METHOD2(WriteToFile,
                bool(const std::string& path, const std::string& str));
   MOCK_CONST_METHOD0(NumberOfCpus, size_t());
+  MOCK_METHOD1(ReadOneCharFromFile, char(const std::string& path));
+  MOCK_METHOD1(ClearFile, bool(const std::string& path));
+  MOCK_CONST_METHOD1(ReadFileIntoString, std::string(const std::string& path));
+
+  bool is_tracing_on() { return tracing_on_; }
+
+ private:
+  bool tracing_on_ = false;
 };
 
 }  // namespace
@@ -143,12 +182,22 @@
 class TestFtraceController : public FtraceController {
  public:
   TestFtraceController(std::unique_ptr<MockFtraceProcfs> ftrace_procfs,
-                       base::TaskRunner* runner,
-                       std::unique_ptr<Table> table)
-      : FtraceController(std::move(ftrace_procfs), runner, std::move(table)) {}
+                       std::unique_ptr<Table> table,
+                       std::unique_ptr<FtraceConfigMuxer> model,
+                       std::unique_ptr<MockTaskRunner> runner,
+                       MockFtraceProcfs* raw_procfs)
+      : FtraceController(std::move(ftrace_procfs),
+                         std::move(table),
+                         std::move(model),
+                         runner.get()),
+        runner_(std::move(runner)),
+        procfs_(raw_procfs) {}
 
   MOCK_METHOD1(OnRawFtraceDataAvailable, void(size_t cpu));
 
+  MockTaskRunner* runner() { return runner_.get(); }
+  MockFtraceProcfs* procfs() { return procfs_; }
+
   uint64_t NowMs() const override { return now_ms; }
 
   uint32_t drain_period_ms() { return GetDrainPeriodMs(); }
@@ -177,147 +226,179 @@
  private:
   TestFtraceController(const TestFtraceController&) = delete;
   TestFtraceController& operator=(const TestFtraceController&) = delete;
+
+  std::unique_ptr<MockTaskRunner> runner_;
+  MockFtraceProcfs* procfs_;
 };
 
+namespace {
+
+std::unique_ptr<TestFtraceController> CreateTestController(
+    bool runner_is_nice_mock,
+    bool procfs_is_nice_mock,
+    size_t cpu_count = 1) {
+  std::unique_ptr<MockTaskRunner> runner;
+  if (runner_is_nice_mock) {
+    runner = std::unique_ptr<MockTaskRunner>(new NiceMock<MockTaskRunner>());
+  } else {
+    runner = std::unique_ptr<MockTaskRunner>(new MockTaskRunner());
+  }
+
+  auto table = FakeTable();
+
+  std::unique_ptr<MockFtraceProcfs> ftrace_procfs;
+  if (procfs_is_nice_mock) {
+    ftrace_procfs = std::unique_ptr<MockFtraceProcfs>(
+        new NiceMock<MockFtraceProcfs>(cpu_count));
+  } else {
+    ftrace_procfs =
+        std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs(cpu_count));
+  }
+
+  MockFtraceProcfs* raw_procfs = ftrace_procfs.get();
+  auto model = FakeModel(ftrace_procfs.get(), table.get());
+  return std::unique_ptr<TestFtraceController>(new TestFtraceController(
+      std::move(ftrace_procfs), std::move(table), std::move(model),
+      std::move(runner), raw_procfs));
+}
+
+}  // namespace
+
 TEST(FtraceControllerTest, NonExistentEventsDontCrash) {
-  NiceMock<MockTaskRunner> task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new NiceMock<MockFtraceProcfs>());
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(true /* nice runner */, true /* nice procfs */);
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"not_an_event"});
 
-  std::unique_ptr<FtraceSink> sink = controller.CreateSink(config, &delegate);
+  std::unique_ptr<FtraceSink> sink = controller->CreateSink(config, &delegate);
 }
 
 TEST(FtraceControllerTest, RejectsBadEventNames) {
-  NiceMock<MockTaskRunner> task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new NiceMock<MockFtraceProcfs>());
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(true /* nice runner */, true /* nice procfs */);
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"../try/to/escape"});
-  EXPECT_FALSE(controller.CreateSink(config, &delegate));
+  EXPECT_FALSE(controller->CreateSink(config, &delegate));
+  EXPECT_FALSE(controller->procfs()->is_tracing_on());
 }
 
 TEST(FtraceControllerTest, OneSink) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(true /* nice runner */, false /* nice procfs */);
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"foo"});
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/tracing_on", "1"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kFooEnablePath, "1"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/buffer_size_kb", _));
-  std::unique_ptr<FtraceSink> sink = controller.CreateSink(config, &delegate);
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _));
+  std::unique_ptr<FtraceSink> sink = controller->CreateSink(config, &delegate);
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kFooEnablePath, "0"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "0"));
+  EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0"));
+  EXPECT_TRUE(controller->procfs()->is_tracing_on());
+
   sink.reset();
+  EXPECT_FALSE(controller->procfs()->is_tracing_on());
 }
 
 TEST(FtraceControllerTest, MultipleSinks) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(false /* nice runner */, false /* nice procfs */);
 
   MockDelegate delegate;
 
   FtraceConfig configA = CreateFtraceConfig({"foo"});
   FtraceConfig configB = CreateFtraceConfig({"foo", "bar"});
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/tracing_on", "1"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/buffer_size_kb", _));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kFooEnablePath, "1"));
-  std::unique_ptr<FtraceSink> sinkA = controller.CreateSink(configA, &delegate);
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1"));
+  std::unique_ptr<FtraceSink> sinkA =
+      controller->CreateSink(configA, &delegate);
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kBarEnablePath, "1"));
-  std::unique_ptr<FtraceSink> sinkB = controller.CreateSink(configB, &delegate);
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kBarEnablePath, "1"));
+  std::unique_ptr<FtraceSink> sinkB =
+      controller->CreateSink(configB, &delegate);
 
   sinkA.reset();
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kFooEnablePath, "0"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kBarEnablePath, "0"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kBarEnablePath, "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0"));
+  EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace"));
   sinkB.reset();
 }
 
 TEST(FtraceControllerTest, ControllerMayDieFirst) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  std::unique_ptr<TestFtraceController> controller(new TestFtraceController(
-      std::move(ftrace_procfs), &task_runner, FakeTable()));
+  auto controller =
+      CreateTestController(false /* nice runner */, false /* nice procfs */);
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"foo"});
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/buffer_size_kb", _));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/tracing_on", "1"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kFooEnablePath, "1"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1"));
   std::unique_ptr<FtraceSink> sink = controller->CreateSink(config, &delegate);
 
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(kFooEnablePath, "0"));
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0"));
+  EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "0"));
+  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0"));
   controller.reset();
 
   sink.reset();
 }
 
 TEST(FtraceControllerTest, TaskScheduling) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs(2u));
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller = CreateTestController(
+      false /* nice runner */, false /* nice procfs */, 2 /* num cpus */);
 
-  // For this test we don't care about calls to WriteToFile.
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(_, _)).Times(AnyNumber());
+  // For this test we don't care about calls to WriteToFile/ClearFile.
+  EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"foo"});
 
-  std::unique_ptr<FtraceSink> sink = controller.CreateSink(config, &delegate);
+  std::unique_ptr<FtraceSink> sink = controller->CreateSink(config, &delegate);
 
   // Only one call to drain should be scheduled for the next drain period.
-  EXPECT_CALL(task_runner, PostDelayedTask(_, 100));
+  EXPECT_CALL(*controller->runner(), PostDelayedTask(_, 100));
 
   // However both CPUs should be drained.
-  EXPECT_CALL(controller, OnRawFtraceDataAvailable(_)).Times(2);
+  EXPECT_CALL(*controller, OnRawFtraceDataAvailable(_)).Times(2);
 
   // Finally, another task should be posted to unblock the workers.
-  EXPECT_CALL(task_runner, PostTask(_));
+  EXPECT_CALL(*controller->runner(), PostTask(_));
 
   // Simulate two worker threads reporting available data.
-  auto on_data_available0 = controller.GetDataAvailableCallback(0u);
+  auto on_data_available0 = controller->GetDataAvailableCallback(0u);
   std::thread worker0([on_data_available0] { on_data_available0(); });
 
-  auto on_data_available1 = controller.GetDataAvailableCallback(1u);
+  auto on_data_available1 = controller->GetDataAvailableCallback(1u);
   std::thread worker1([on_data_available1] { on_data_available1(); });
 
   // Poll until both worker threads have reported available data.
-  controller.WaitForData(0u);
-  controller.WaitForData(1u);
+  controller->WaitForData(0u);
+  controller->WaitForData(1u);
 
   // Run the task to drain all CPUs.
-  task_runner.RunLastTask();
+  controller->runner()->RunLastTask();
 
   // Run the task to unblock all workers.
-  task_runner.RunLastTask();
+  controller->runner()->RunLastTask();
 
   worker0.join();
   worker1.join();
@@ -327,42 +408,40 @@
 
 // TODO(b/73452932): Fix and reenable this test.
 TEST(FtraceControllerTest, DISABLED_DrainPeriodRespected) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(false /* nice runner */, false /* nice procfs */);
 
-  // For this test we don't care about calls to WriteToFile.
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(_, _)).Times(AnyNumber());
+  // For this test we don't care about calls to WriteToFile/ClearFile.
+  EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"foo"});
 
   // Test several cycles of a worker producing data and make sure the drain
   // delay is consistent with the drain period.
-  std::unique_ptr<FtraceSink> sink = controller.CreateSink(config, &delegate);
+  std::unique_ptr<FtraceSink> sink = controller->CreateSink(config, &delegate);
 
   const int kCycles = 50;
-  EXPECT_CALL(task_runner, PostDelayedTask(_, controller.drain_period_ms()))
+  EXPECT_CALL(*controller->runner(),
+              PostDelayedTask(_, controller->drain_period_ms()))
       .Times(kCycles);
-  EXPECT_CALL(controller, OnRawFtraceDataAvailable(_)).Times(kCycles);
-  EXPECT_CALL(task_runner, PostTask(_)).Times(kCycles);
+  EXPECT_CALL(*controller, OnRawFtraceDataAvailable(_)).Times(kCycles);
+  EXPECT_CALL(*controller->runner(), PostTask(_)).Times(kCycles);
 
   // Simulate a worker thread continually reporting pages of available data.
-  auto on_data_available = controller.GetDataAvailableCallback(0u);
+  auto on_data_available = controller->GetDataAvailableCallback(0u);
   std::thread worker([on_data_available] {
     for (int i = 0; i < kCycles; i++)
       on_data_available();
   });
 
   for (int i = 0; i < kCycles; i++) {
-    controller.WaitForData(0u);
+    controller->WaitForData(0u);
     // Run two tasks: one to drain each CPU and another to unblock the worker.
-    task_runner.RunLastTask();
-    task_runner.RunLastTask();
-    controller.now_ms += controller.drain_period_ms();
+    controller->runner()->RunLastTask();
+    controller->runner()->RunLastTask();
+    controller->now_ms += controller->drain_period_ms();
   }
 
   worker.join();
@@ -370,150 +449,148 @@
 }
 
 TEST(FtraceControllerTest, BackToBackEnableDisable) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(false /* nice runner */, false /* nice procfs */);
 
-  // For this test we don't care about calls to WriteToFile.
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(_, _)).Times(AnyNumber());
+  // For this test we don't care about calls to WriteToFile/ClearFile.
+  EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
+  EXPECT_CALL(*controller->procfs(), ReadOneCharFromFile("/root/tracing_on"))
+      .Times(AnyNumber());
 
   MockDelegate delegate;
   FtraceConfig config = CreateFtraceConfig({"foo"});
 
-  EXPECT_CALL(task_runner, PostDelayedTask(_, 100)).Times(2);
-  std::unique_ptr<FtraceSink> sink_a = controller.CreateSink(config, &delegate);
+  EXPECT_CALL(*controller->runner(), PostDelayedTask(_, 100)).Times(2);
+  std::unique_ptr<FtraceSink> sink_a =
+      controller->CreateSink(config, &delegate);
 
-  auto on_data_available = controller.GetDataAvailableCallback(0u);
+  auto on_data_available = controller->GetDataAvailableCallback(0u);
   std::thread worker([on_data_available] { on_data_available(); });
-  controller.WaitForData(0u);
+  controller->WaitForData(0u);
 
   // Disable the first sink and run the delayed task that it generated. It
   // should be a no-op.
   sink_a.reset();
-  task_runner.RunLastTask();
+  controller->runner()->RunLastTask();
   worker.join();
 
   // Register another sink and wait for it to generate data.
-  std::unique_ptr<FtraceSink> sink_b = controller.CreateSink(config, &delegate);
+  std::unique_ptr<FtraceSink> sink_b =
+      controller->CreateSink(config, &delegate);
   std::thread worker2([on_data_available] { on_data_available(); });
-  controller.WaitForData(0u);
+  controller->WaitForData(0u);
 
   // This drain should also be a no-op after the sink is unregistered.
   sink_b.reset();
-  task_runner.RunLastTask();
+  controller->runner()->RunLastTask();
   worker2.join();
 }
 
 TEST(FtraceControllerTest, BufferSize) {
-  NiceMock<MockTaskRunner> task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(true /* nice runner */, false /* nice procfs */);
 
-  // For this test we don't care about most calls to WriteToFile.
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(_, _)).Times(AnyNumber());
+  // For this test we don't care about most calls to WriteToFile/ClearFile.
+  EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
   MockDelegate delegate;
 
   {
     // No buffer size -> good default.
     // 8192kb = 8mb
-    EXPECT_CALL(*raw_ftrace_procfs,
-                WriteToFile("/root/buffer_size_kb", "4096"));
+    EXPECT_CALL(*controller->procfs(),
+                WriteToFile("/root/buffer_size_kb", "512"));
     FtraceConfig config = CreateFtraceConfig({"foo"});
-    auto sink = controller.CreateSink(config, &delegate);
+    auto sink = controller->CreateSink(config, &delegate);
   }
 
   {
     // Way too big buffer size -> good default.
-    EXPECT_CALL(*raw_ftrace_procfs,
-                WriteToFile("/root/buffer_size_kb", "4096"));
+    EXPECT_CALL(*controller->procfs(),
+                WriteToFile("/root/buffer_size_kb", "512"));
     FtraceConfig config = CreateFtraceConfig({"foo"});
     config.set_buffer_size_kb(10 * 1024 * 1024);
-    auto sink = controller.CreateSink(config, &delegate);
+    auto sink = controller->CreateSink(config, &delegate);
   }
 
   {
     // The limit is 8mb, 9mb is too much.
-    EXPECT_CALL(*raw_ftrace_procfs,
-                WriteToFile("/root/buffer_size_kb", "4096"));
+    EXPECT_CALL(*controller->procfs(),
+                WriteToFile("/root/buffer_size_kb", "512"));
     FtraceConfig config = CreateFtraceConfig({"foo"});
-    ON_CALL(*raw_ftrace_procfs, NumberOfCpus()).WillByDefault(Return(2));
+    ON_CALL(*controller->procfs(), NumberOfCpus()).WillByDefault(Return(2));
     config.set_buffer_size_kb(9 * 1024);
-    auto sink = controller.CreateSink(config, &delegate);
+    auto sink = controller->CreateSink(config, &delegate);
   }
 
   {
     // Your size ends up with less than 1 page per cpu -> 1 page.
-    EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/buffer_size_kb", "4"));
+    EXPECT_CALL(*controller->procfs(),
+                WriteToFile("/root/buffer_size_kb", "4"));
     FtraceConfig config = CreateFtraceConfig({"foo"});
     config.set_buffer_size_kb(1);
-    auto sink = controller.CreateSink(config, &delegate);
+    auto sink = controller->CreateSink(config, &delegate);
   }
 
   {
     // You picked a good size -> your size rounded to nearest page.
-    EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/buffer_size_kb", "40"));
+    EXPECT_CALL(*controller->procfs(),
+                WriteToFile("/root/buffer_size_kb", "40"));
     FtraceConfig config = CreateFtraceConfig({"foo"});
     config.set_buffer_size_kb(42);
-    auto sink = controller.CreateSink(config, &delegate);
+    auto sink = controller->CreateSink(config, &delegate);
   }
 
   {
     // You picked a good size -> your size rounded to nearest page.
-    EXPECT_CALL(*raw_ftrace_procfs, WriteToFile("/root/buffer_size_kb", "40"));
+    EXPECT_CALL(*controller->procfs(),
+                WriteToFile("/root/buffer_size_kb", "40"));
     FtraceConfig config = CreateFtraceConfig({"foo"});
-    ON_CALL(*raw_ftrace_procfs, NumberOfCpus()).WillByDefault(Return(2));
+    ON_CALL(*controller->procfs(), NumberOfCpus()).WillByDefault(Return(2));
     config.set_buffer_size_kb(42);
-    auto sink = controller.CreateSink(config, &delegate);
+    auto sink = controller->CreateSink(config, &delegate);
   }
 }
 
 TEST(FtraceControllerTest, PeriodicDrainConfig) {
-  MockTaskRunner task_runner;
-  auto ftrace_procfs =
-      std::unique_ptr<MockFtraceProcfs>(new MockFtraceProcfs());
-  auto raw_ftrace_procfs = ftrace_procfs.get();
-  TestFtraceController controller(std::move(ftrace_procfs), &task_runner,
-                                  FakeTable());
+  auto controller =
+      CreateTestController(true /* nice runner */, false /* nice procfs */);
 
-  // For this test we don't care about calls to WriteToFile.
-  EXPECT_CALL(*raw_ftrace_procfs, WriteToFile(_, _)).Times(AnyNumber());
+  // For this test we don't care about calls to WriteToFile/ClearFile.
+  EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
   MockDelegate delegate;
 
   {
     // No period -> good default.
     FtraceConfig config = CreateFtraceConfig({"foo"});
-    auto sink = controller.CreateSink(config, &delegate);
-    EXPECT_EQ(100u, controller.drain_period_ms());
+    auto sink = controller->CreateSink(config, &delegate);
+    EXPECT_EQ(100u, controller->drain_period_ms());
   }
 
   {
     // Pick a tiny value -> good default.
     FtraceConfig config = CreateFtraceConfig({"foo"});
     config.set_drain_period_ms(0);
-    auto sink = controller.CreateSink(config, &delegate);
-    EXPECT_EQ(100u, controller.drain_period_ms());
+    auto sink = controller->CreateSink(config, &delegate);
+    EXPECT_EQ(100u, controller->drain_period_ms());
   }
 
   {
     // Pick a huge value -> good default.
     FtraceConfig config = CreateFtraceConfig({"foo"});
     config.set_drain_period_ms(1000 * 60 * 60);
-    auto sink = controller.CreateSink(config, &delegate);
-    EXPECT_EQ(100u, controller.drain_period_ms());
+    auto sink = controller->CreateSink(config, &delegate);
+    EXPECT_EQ(100u, controller->drain_period_ms());
   }
 
   {
     // Pick a resonable value -> get that value.
     FtraceConfig config = CreateFtraceConfig({"foo"});
     config.set_drain_period_ms(200);
-    auto sink = controller.CreateSink(config, &delegate);
-    EXPECT_EQ(200u, controller.drain_period_ms());
+    auto sink = controller->CreateSink(config, &delegate);
+    EXPECT_EQ(200u, controller->drain_period_ms());
   }
 }
 
diff --git a/src/ftrace_reader/ftrace_procfs.cc b/src/ftrace_reader/ftrace_procfs.cc
index f3f96f8..1eef505 100644
--- a/src/ftrace_reader/ftrace_procfs.cc
+++ b/src/ftrace_reader/ftrace_procfs.cc
@@ -29,7 +29,6 @@
 #include "perfetto/base/utils.h"
 
 namespace perfetto {
-namespace {
 
 // Reading /trace produces human readable trace output.
 // Writing to this file clears all trace buffers for all CPUS.
@@ -41,31 +40,7 @@
 // Disabling tracing with this file prevents further writes but
 // does not clear the buffer.
 
-char ReadOneCharFromFile(const std::string& path) {
-  base::ScopedFile fd = base::OpenFile(path.c_str(), O_RDONLY);
-  PERFETTO_CHECK(fd);
-  char result = '\0';
-  ssize_t bytes = PERFETTO_EINTR(read(fd.get(), &result, 1));
-  PERFETTO_CHECK(bytes == 1 || bytes == -1);
-  return result;
-}
-
-std::string ReadFileIntoString(const std::string& path) {
-  std::ifstream fin(path, std::ios::in);
-  if (!fin) {
-    PERFETTO_DLOG("Could not read '%s'", path.c_str());
-    return "";
-  }
-
-  std::string str;
-  // You can't seek or stat the procfs files on Android.
-  // The vast majority (884/886) of format files are under 4k.
-  str.reserve(4096);
-  str.assign(std::istreambuf_iterator<char>(fin),
-             std::istreambuf_iterator<char>());
-
-  return str;
-}
+namespace {
 
 int OpenKmesgFD() {
   // This environment variable gets set to a fd to /dev/kmsg opened for writing.
@@ -126,8 +101,7 @@
 
 void FtraceProcfs::ClearTrace() {
   std::string path = root_ + "trace";
-  base::ScopedFile fd = base::OpenFile(path.c_str(), O_WRONLY | O_TRUNC);
-  PERFETTO_CHECK(fd);  // Could not clear.
+  PERFETTO_CHECK(ClearFile(path));  // Could not clear.
 }
 
 bool FtraceProcfs::WriteTraceMarker(const std::string& str) {
@@ -156,6 +130,10 @@
   return WriteToFile(path, "0");
 }
 
+bool FtraceProcfs::SetTracingOn(bool enable) {
+  return enable ? EnableTracing() : DisableTracing();
+}
+
 bool FtraceProcfs::IsTracingEnabled() {
   std::string path = root_ + "tracing_on";
   return ReadOneCharFromFile(path) == '1';
@@ -189,7 +167,13 @@
   size_t start = 0;
   size_t end = 0;
 
-  while ((end = s.find(" ", start)) != std::string::npos) {
+  while (true) {
+    end = s.find(" ", start);
+    if (end == std::string::npos)
+      end = s.size();
+    if (start == end)
+      break;
+
     std::string name = s.substr(start, end - start);
 
     if (name[0] == '[')
@@ -197,6 +181,9 @@
 
     names.insert(name);
 
+    if (end == s.size())
+      break;
+
     start = end + 1;
   }
 
@@ -230,6 +217,37 @@
   return base::OpenFile(path.c_str(), O_RDONLY | O_NONBLOCK);
 }
 
+char FtraceProcfs::ReadOneCharFromFile(const std::string& path) {
+  base::ScopedFile fd = base::OpenFile(path.c_str(), O_RDONLY);
+  PERFETTO_CHECK(fd);
+  char result = '\0';
+  ssize_t bytes = PERFETTO_EINTR(read(fd.get(), &result, 1));
+  PERFETTO_CHECK(bytes == 1 || bytes == -1);
+  return result;
+}
+
+bool FtraceProcfs::ClearFile(const std::string& path) {
+  base::ScopedFile fd = base::OpenFile(path.c_str(), O_WRONLY | O_TRUNC);
+  return !!fd;
+}
+
+std::string FtraceProcfs::ReadFileIntoString(const std::string& path) const {
+  std::ifstream fin(path, std::ios::in);
+  if (!fin) {
+    PERFETTO_DLOG("Could not read '%s'", path.c_str());
+    return "";
+  }
+
+  std::string str;
+  // You can't seek or stat the procfs files on Android.
+  // The vast majority (884/886) of format files are under 4k.
+  str.reserve(4096);
+  str.assign(std::istreambuf_iterator<char>(fin),
+             std::istreambuf_iterator<char>());
+
+  return str;
+}
+
 // static
 bool FtraceProcfs::CheckRootPath(const std::string& root) {
   base::ScopedFile fd = base::OpenFile(root + "trace", O_RDONLY);
diff --git a/src/ftrace_reader/ftrace_procfs.h b/src/ftrace_reader/ftrace_procfs.h
index 1504d29..10fbda9 100644
--- a/src/ftrace_reader/ftrace_procfs.h
+++ b/src/ftrace_reader/ftrace_procfs.h
@@ -67,6 +67,9 @@
   // Disables tracing, does not clear the buffer.
   bool DisableTracing();
 
+  // Enabls/disables tracing, does not clear the buffer.
+  bool SetTracingOn(bool enable);
+
   // Returns true iff tracing is enabled.
   // Necessarily racy: another program could enable/disable tracing at any
   // point.
@@ -85,8 +88,12 @@
   // Open the raw pipe for |cpu|.
   virtual base::ScopedFile OpenPipeForCpu(size_t cpu);
 
+ protected:
   // virtual and public for testing.
   virtual bool WriteToFile(const std::string& path, const std::string& str);
+  virtual bool ClearFile(const std::string& path);
+  virtual char ReadOneCharFromFile(const std::string& path);
+  virtual std::string ReadFileIntoString(const std::string& path) const;
 
  private:
   // Checks the trace file is present at the given root path.
diff --git a/src/ftrace_reader/ftrace_procfs_unittest.cc b/src/ftrace_reader/ftrace_procfs_unittest.cc
new file mode 100644
index 0000000..4a6bc7d
--- /dev/null
+++ b/src/ftrace_reader/ftrace_procfs_unittest.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ftrace_procfs.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using testing::AnyNumber;
+using testing::IsEmpty;
+using testing::Return;
+using testing::UnorderedElementsAre;
+
+namespace perfetto {
+namespace {
+
+class MockFtraceProcfs : public FtraceProcfs {
+ public:
+  MockFtraceProcfs() : FtraceProcfs("/root/") {}
+
+  MOCK_METHOD2(WriteToFile,
+               bool(const std::string& path, const std::string& str));
+  MOCK_METHOD1(ReadOneCharFromFile, char(const std::string& path));
+  MOCK_METHOD1(ClearFile, bool(const std::string& path));
+  MOCK_CONST_METHOD1(ReadFileIntoString, std::string(const std::string& path));
+  MOCK_CONST_METHOD0(NumberOfCpus, size_t());
+};
+
+TEST(FtraceProcfsTest, ParseAvailableClocks) {
+  MockFtraceProcfs ftrace;
+
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillOnce(Return("[local] global boot"));
+  EXPECT_THAT(ftrace.AvailableClocks(),
+              UnorderedElementsAre("local", "global", "boot"));
+
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillOnce(Return("[local] global boot"));
+  EXPECT_THAT(ftrace.GetClock(), "local");
+
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillOnce(Return("local [global] boot"));
+  EXPECT_THAT(ftrace.GetClock(), "global");
+
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillOnce(Return(""));
+  EXPECT_THAT(ftrace.AvailableClocks(), IsEmpty());
+}
+
+}  // namespace
+}  // namespace perfetto