perfetto: Dynamically create and remove trace event triggers

Using a persistent event trigger for rss_stat_throttled was
found to cause regression of 8% more CPU cycles in kswapd.

Have perfetto dynamically create these triggers when needed
and cleanup the trigger once the perfetto session no longer
needs it.

Triggers are created/removed when the associated trace
event is enabled/disabled by perfetto, so multiple sessions
with the same event trigger are inherently handled correctly
by the same ftrace_config_muxer logic for enabling/disabling
ftrace events.

The persistent rss_stat_throttled histogram trigger will be changed
to an as-needed trigger created by perfetto. So the existence of
this event trigger can no longer be used to check if the kernel
supports throttling rss_stat event.

Instead test creating a histogram trigger with division
arithmetic (needed for event throttling) on rss_stat_throttled.
Creating the trigger on rss_stat_throttled also implicitly checks
that the synthetic throttled event is present.

Test: adb push test/configs/rss_stat.cfg /data/local/tmp/
        && cf-adb shell 'cat /data/local/tmp/rss_stat.cfg |
	   perfetto --txt -c - -o /data/misc/perfetto-traces/trace
Bug: 236172293
Change-Id: I9e14d56bb442129ea85163e492e00382eb8d8994
Merged-In: I9e14d56bb442129ea85163e492e00382eb8d8994
Merged-In: Ifa50c9e08893c8494739747fe77ab1ceec5c0dfd
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 6d3a546..69c4176 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -95,12 +95,6 @@
   *out = std::move(v);
 }
 
-bool SupportsRssStatThrottled(const FtraceProcfs& ftrace_procfs) {
-  const auto trigger_info = ftrace_procfs.ReadEventTrigger("kmem", "rss_stat");
-
-  return trigger_info.find("rss_stat_throttled") != std::string::npos;
-}
-
 // This is just to reduce binary size and stack frame size of the insertions.
 // It effectively undoes STL's set::insert inlining.
 void PERFETTO_NO_INLINE InsertEvent(const char* group,
@@ -416,7 +410,7 @@
 
       if (category == "memory") {
         // Use rss_stat_throttled if supported
-        if (SupportsRssStatThrottled(*ftrace_)) {
+        if (ftrace_->SupportsRssStatThrottled()) {
           InsertEvent("synthetic", "rss_stat_throttled", &events);
         } else {
           InsertEvent("kmem", "rss_stat", &events);
@@ -440,7 +434,7 @@
   }
 
   // If throttle_rss_stat: true, use the rss_stat_throttled event if supported
-  if (request.throttle_rss_stat() && SupportsRssStatThrottled(*ftrace_)) {
+  if (request.throttle_rss_stat() && ftrace_->SupportsRssStatThrottled()) {
     auto it = std::find_if(
         events.begin(), events.end(), [](const GroupAndName& event) {
           return event.group() == "kmem" && event.name() == "rss_stat";
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index e393648..55b81b4 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -45,6 +45,12 @@
 
 namespace {
 
+namespace {
+constexpr char kRssStatThrottledTrigger[] =
+    "hist:keys=mm_id,member:bucket=size/0x80000"
+    ":onchange($bucket).rss_stat_throttled(mm_id,curr,member,size)";
+}
+
 void KernelLogWrite(const char* s) {
   PERFETTO_DCHECK(*s && s[strlen(s) - 1] == '\n');
   if (FtraceProcfs::g_kmesg_fd != -1)
@@ -104,6 +110,11 @@
 bool FtraceProcfs::EnableEvent(const std::string& group,
                                const std::string& name) {
   std::string path = root_ + "events/" + group + "/" + name + "/enable";
+
+  // Create any required triggers for the ftrace event being enabled.
+  // Some ftrace events (synthetic events) need to set up an event trigger
+  MaybeSetUpEventTriggers(group, name);
+
   if (WriteToFile(path, "1"))
     return true;
   path = root_ + "set_event";
@@ -113,10 +124,17 @@
 bool FtraceProcfs::DisableEvent(const std::string& group,
                                 const std::string& name) {
   std::string path = root_ + "events/" + group + "/" + name + "/enable";
-  if (WriteToFile(path, "0"))
-    return true;
-  path = root_ + "set_event";
-  return AppendToFile(path, "!" + group + ":" + name);
+
+  bool ret = WriteToFile(path, "0");
+  if (!ret) {
+    path = root_ + "set_event";
+    ret = AppendToFile(path, "!" + group + ":" + name);
+  }
+
+  // Remove any associated event triggers after disabling the event
+  MaybeTearDownEventTriggers(group, name);
+
+  return ret;
 }
 
 bool FtraceProcfs::DisableAllEvents() {
@@ -130,10 +148,110 @@
   return ReadFileIntoString(path);
 }
 
-std::string FtraceProcfs::ReadEventTrigger(const std::string& group,
-                                           const std::string& name) const {
+std::vector<std::string> FtraceProcfs::ReadEventTriggers(
+    const std::string& group,
+    const std::string& name) const {
   std::string path = root_ + "events/" + group + "/" + name + "/trigger";
-  return ReadFileIntoString(path);
+  std::string s = ReadFileIntoString(path);
+  std::vector<std::string> triggers;
+
+  for (base::StringSplitter ss(s, '\n'); ss.Next();) {
+    std::string trigger = ss.cur_token();
+    if (trigger.empty() || trigger[0] == '#')
+      continue;
+
+    base::StringSplitter ts(trigger, ' ');
+    PERFETTO_CHECK(ts.Next());
+    triggers.push_back(ts.cur_token());
+  }
+
+  return triggers;
+}
+
+bool FtraceProcfs::CreateEventTrigger(const std::string& group,
+                                      const std::string& name,
+                                      const std::string& trigger) {
+  std::string path = root_ + "events/" + group + "/" + name + "/trigger";
+  return WriteToFile(path, trigger);
+}
+
+bool FtraceProcfs::RemoveEventTrigger(const std::string& group,
+                                      const std::string& name,
+                                      const std::string& trigger) {
+  std::string path = root_ + "events/" + group + "/" + name + "/trigger";
+  return WriteToFile(path, "!" + trigger);
+}
+
+bool FtraceProcfs::RemoveAllEventTriggers(const std::string& group,
+                                          const std::string& name) {
+  std::vector<std::string> triggers = ReadEventTriggers(group, name);
+
+  // Remove the triggers in reverse order since a trigger can depend
+  // on another trigger created earlier.
+  for (auto it = triggers.rbegin(); it != triggers.rend(); ++it)
+    if (!RemoveEventTrigger(group, name, *it))
+      return false;
+  return true;
+}
+
+bool FtraceProcfs::MaybeSetUpEventTriggers(const std::string& group,
+                                           const std::string& name) {
+  bool ret = true;
+
+  if (group == "synthetic" && name == "rss_stat_throttled") {
+    ret = RemoveAllEventTriggers("kmem", "rss_stat") &&
+          CreateEventTrigger("kmem", "rss_stat", kRssStatThrottledTrigger);
+  }
+
+  if (!ret) {
+    PERFETTO_PLOG("Failed to setup event triggers for %s:%s", group.c_str(),
+                  name.c_str());
+  }
+
+  return ret;
+}
+
+bool FtraceProcfs::MaybeTearDownEventTriggers(const std::string& group,
+                                              const std::string& name) {
+  bool ret = true;
+
+  if (group == "synthetic" && name == "rss_stat_throttled")
+    ret = RemoveAllEventTriggers("kmem", "rss_stat");
+
+  if (!ret) {
+    PERFETTO_PLOG("Failed to tear down event triggers for: %s:%s",
+                  group.c_str(), name.c_str());
+  }
+
+  return ret;
+}
+
+bool FtraceProcfs::SupportsRssStatThrottled() {
+  std::string group = "synthetic";
+  std::string name = "rss_stat_throttled";
+
+  // Check if the trigger already exists. Don't try recreating
+  // or removing the trigger if it is already in use.
+  auto triggers = ReadEventTriggers("kmem", "rss_stat");
+  for (const auto& trigger : triggers) {
+    // The kernel shows all the default values of a trigger
+    // when read from and trace event 'trigger' file.
+    //
+    // Trying to match the complete trigger string is prone
+    // to fail if, in the future, the kernel changes default
+    // fields or values for event triggers.
+    //
+    // Do a partial match on the generated event name
+    // (rss_stat_throttled) to detect if the trigger
+    // is already created.
+    if (trigger.find(name) != std::string::npos)
+      return true;
+  }
+
+  // Attempt to create rss_stat_throttled hist trigger */
+  bool ret = MaybeSetUpEventTriggers(group, name);
+
+  return ret && MaybeTearDownEventTriggers(group, name);
 }
 
 std::string FtraceProcfs::ReadPrintkFormats() const {
diff --git a/src/traced/probes/ftrace/ftrace_procfs.h b/src/traced/probes/ftrace/ftrace_procfs.h
index 6e0791b..b7ddf99 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.h
+++ b/src/traced/probes/ftrace/ftrace_procfs.h
@@ -58,9 +58,34 @@
 
   virtual std::string ReadPageHeaderFormat() const;
 
-  // Read the triggers for event with the given |group| and |name|.
-  std::string ReadEventTrigger(const std::string& group,
-                               const std::string& name) const;
+  // Get all triggers for event with the given |group| and |name|.
+  std::vector<std::string> ReadEventTriggers(const std::string& group,
+                                             const std::string& name) const;
+
+  // Create an event trigger for the given |group| and |name|.
+  bool CreateEventTrigger(const std::string& group,
+                          const std::string& name,
+                          const std::string& trigger);
+
+  // Remove an event trigger for the given |group| and |name|.
+  bool RemoveEventTrigger(const std::string& group,
+                          const std::string& name,
+                          const std::string& trigger);
+
+  // Remove all event trigger for the given |group| and |name|.
+  bool RemoveAllEventTriggers(const std::string& group,
+                              const std::string& name);
+
+  // Sets up any associated event trigger before enabling the event
+  bool MaybeSetUpEventTriggers(const std::string& group,
+                               const std::string& name);
+
+  // Tears down any associated event trigger after disabling the event
+  bool MaybeTearDownEventTriggers(const std::string& group,
+                                  const std::string& name);
+
+  // Returns true if rss_stat_throttled synthetic event is supported
+  bool SupportsRssStatThrottled();
 
   // Read the printk formats file.
   std::string ReadPrintkFormats() const;