Move MaybeReleaseAllocatorMemToOS to base

This CL refactors the calls to mallopt(M_PURGE) into
a base/utils.h function. It also makes it work consistently
on standalone builds using dlsym.
Unfortunately looks like we will have to put more workarounds
to deal with Scudo edge cases. See bug for discussion.
This CL will be used by the upcoming kallsyms symbolizer.

Bug: 170217718
Change-Id: I4d418aac2ab6bb90120f1703e1055426e54c59a5
diff --git a/Android.bp b/Android.bp
index 0f0cc57..3205c62 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6023,6 +6023,7 @@
     "src/base/thread_task_runner.cc",
     "src/base/time.cc",
     "src/base/unix_task_runner.cc",
+    "src/base/utils.cc",
     "src/base/uuid.cc",
     "src/base/virtual_destructors.cc",
     "src/base/waitable_event.cc",
diff --git a/BUILD b/BUILD
index b50e0da..aa3104e 100644
--- a/BUILD
+++ b/BUILD
@@ -554,6 +554,7 @@
         "src/base/thread_task_runner.cc",
         "src/base/time.cc",
         "src/base/unix_task_runner.cc",
+        "src/base/utils.cc",
         "src/base/uuid.cc",
         "src/base/virtual_destructors.cc",
         "src/base/waitable_event.cc",
diff --git a/include/perfetto/ext/base/utils.h b/include/perfetto/ext/base/utils.h
index b702d4f..35b5ef7 100644
--- a/include/perfetto/ext/base/utils.h
+++ b/include/perfetto/ext/base/utils.h
@@ -125,6 +125,11 @@
   return err == EAGAIN || err == EWOULDBLOCK;
 }
 
+// Calls mallopt(M_PURGE, 0) on Android. Does nothing on other platforms.
+// This forces the allocator to release freed memory. This is used to work
+// around various Scudo inefficiencies. See b/170217718.
+void MaybeReleaseAllocatorMemToOS();
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 055d70d..146bd77 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -34,6 +34,7 @@
     "subprocess.cc",
     "thread_checker.cc",
     "time.cc",
+    "utils.cc",
     "uuid.cc",
     "virtual_destructors.cc",
     "waitable_event.cc",
diff --git a/src/base/utils.cc b/src/base/utils.cc
new file mode 100644
index 0000000..5dfe1e1
--- /dev/null
+++ b/src/base/utils.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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 "perfetto/ext/base/utils.h"
+
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <dlfcn.h>
+#include <malloc.h>
+
+#ifdef M_PURGE
+#define PERFETTO_M_PURGE M_PURGE
+#else
+// Only available in in-tree builds and on newer SDKs.
+#define PERFETTO_M_PURGE -101
+#endif
+
+namespace {
+extern "C" {
+using MalloptType = void (*)(int, int);
+}
+}  // namespace
+#endif  // OS_ANDROID
+
+namespace perfetto {
+namespace base {
+
+void MaybeReleaseAllocatorMemToOS() {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  // mallopt() on Android requires SDK level 26. Many targets and embedders
+  // still depend on a lower SDK level. Given mallopt() is a quite simple API,
+  // use reflection to do this rather than bumping the SDK level for all
+  // embedders. This keeps the behavior of standalone builds aligned with
+  // in-tree builds.
+  static MalloptType mallopt_fn =
+      reinterpret_cast<MalloptType>(dlsym(RTLD_DEFAULT, "mallopt"));
+  if (!mallopt_fn)
+    return;
+  mallopt_fn(PERFETTO_M_PURGE, 0);
+#endif
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 01ddb06..2c080fd 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -19,7 +19,6 @@
 #include <random>
 #include <utility>
 
-#include <malloc.h>
 #include <unistd.h>
 
 #include <unwindstack/Error.h>
@@ -28,6 +27,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
 #include "perfetto/ext/base/metatrace.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/producer.h"
@@ -135,16 +135,6 @@
   return true;
 }
 
-void MaybeReleaseAllocatorMemToOS() {
-#if defined(__BIONIC__)
-  // TODO(b/152414415): libunwindstack's volume of small allocations is
-  // adverarial to scudo, which doesn't automatically release small
-  // allocation regions back to the OS. Forceful purge does reclaim all size
-  // classes.
-  mallopt(M_PURGE, 0);
-#endif
-}
-
 protos::pbzero::Profiling::CpuMode ToCpuModeEnum(uint16_t perf_cpu_mode) {
   using Profiling = protos::pbzero::Profiling;
   switch (perf_cpu_mode) {
@@ -726,7 +716,7 @@
   // Clean up resources if there are no more active sources.
   if (data_sources_.empty()) {
     callstack_trie_.ClearTrie();  // purge internings
-    MaybeReleaseAllocatorMemToOS();
+    base::MaybeReleaseAllocatorMemToOS();
   }
 }
 
diff --git a/src/profiling/perf/unwinding.cc b/src/profiling/perf/unwinding.cc
index c86a8c0..fb310cc 100644
--- a/src/profiling/perf/unwinding.cc
+++ b/src/profiling/perf/unwinding.cc
@@ -22,21 +22,11 @@
 
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/base/thread_utils.h"
+#include "perfetto/ext/base/utils.h"
 
 namespace {
 constexpr size_t kUnwindingMaxFrames = 1000;
 constexpr uint32_t kDataSourceShutdownRetryDelayMs = 400;
-
-void MaybeReleaseAllocatorMemToOS() {
-#if defined(__BIONIC__)
-  // TODO(b/152414415): libunwindstack's volume of small allocations is
-  // adverarial to scudo, which doesn't automatically release small
-  // allocation regions back to the OS. Forceful purge does reclaim all size
-  // classes.
-  mallopt(M_PURGE, 0);
-#endif
-}
-
 }  // namespace
 
 namespace perfetto {
@@ -456,7 +446,7 @@
     pid_and_process.second.unwind_state->fd_maps.Reset();
   }
   ResetAndEnableUnwindstackCache();
-  MaybeReleaseAllocatorMemToOS();
+  base::MaybeReleaseAllocatorMemToOS();
 
   PostClearCachedStatePeriodic(ds_id, period_ms);  // repost
 }