profiling: Add management for system properties.

Change-Id: I6dea624542b3429086d4641a5fa91032a97709d1
diff --git a/Android.bp b/Android.bp
index 4eb1891..6e4f684 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,7 @@
     "src/profiling/memory/main.cc",
     "src/profiling/memory/record_reader.cc",
     "src/profiling/memory/socket_listener.cc",
+    "src/profiling/memory/system_property.cc",
     "src/profiling/memory/unwinding.cc",
     "src/profiling/memory/wire_protocol.cc",
     "src/protozero/message.cc",
@@ -2299,6 +2300,8 @@
     "src/profiling/memory/sampler_unittest.cc",
     "src/profiling/memory/socket_listener.cc",
     "src/profiling/memory/socket_listener_unittest.cc",
+    "src/profiling/memory/system_property.cc",
+    "src/profiling/memory/system_property_unittest.cc",
     "src/profiling/memory/unwinding.cc",
     "src/profiling/memory/unwinding_unittest.cc",
     "src/profiling/memory/wire_protocol.cc",
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 9514ea9..14745ac 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -63,6 +63,8 @@
     "record_reader.h",
     "socket_listener.cc",
     "socket_listener.h",
+    "system_property.cc",
+    "system_property.h",
     "unwinding.cc",
     "unwinding.h",
   ]
@@ -106,6 +108,7 @@
     "record_reader_unittest.cc",
     "sampler_unittest.cc",
     "socket_listener_unittest.cc",
+    "system_property_unittest.cc",
     "unwinding_unittest.cc",
     "wire_protocol_unittest.cc",
   ]
diff --git a/src/profiling/memory/system_property.cc b/src/profiling/memory/system_property.cc
new file mode 100644
index 0000000..499bc6b
--- /dev/null
+++ b/src/profiling/memory/system_property.cc
@@ -0,0 +1,128 @@
+/*
+ * 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 "src/profiling/memory/system_property.h"
+
+#include "perfetto/base/logging.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <sys/system_properties.h>
+#endif
+
+namespace perfetto {
+namespace profiling {
+
+SystemProperties::Handle::Handle(Handle&& other) {
+  system_properties_ = other.system_properties_;
+  property_ = std::move(other.property_);
+  all_ = other.all_;
+  other.system_properties_ = nullptr;
+}
+
+SystemProperties::Handle& SystemProperties::Handle::operator=(Handle&& other) {
+  system_properties_ = other.system_properties_;
+  property_ = std::move(other.property_);
+  all_ = other.all_;
+  other.system_properties_ = nullptr;
+  return *this;
+}
+
+SystemProperties::Handle::Handle(SystemProperties* system_properties)
+    : system_properties_(system_properties), all_(true) {}
+
+SystemProperties::Handle::Handle(SystemProperties* system_properties,
+                                 std::string property)
+    : system_properties_(system_properties), property_(std::move(property)) {}
+
+SystemProperties::Handle::~Handle() {
+  if (system_properties_) {
+    if (all_)
+      system_properties_->UnsetAll();
+    else
+      system_properties_->UnsetProperty(property_);
+  }
+}
+
+SystemProperties::Handle::operator bool() {
+  return system_properties_ != nullptr;
+}
+
+SystemProperties::Handle SystemProperties::SetProperty(std::string name) {
+  auto it = properties_.find(name);
+  if (it == properties_.end()) {
+    if (!SetAndroidProperty("heapprofd.enable." + name, "1"))
+      return Handle(nullptr);
+    if (properties_.size() == 1 || alls_ == 0) {
+      if (!SetAndroidProperty("heapprofd.enable", "1"))
+        return Handle(nullptr);
+    }
+    properties_.emplace(name, 1);
+  } else {
+    it->second++;
+  }
+  return Handle(this, std::move(name));
+}
+
+SystemProperties::Handle SystemProperties::SetAll() {
+  if (alls_ == 0) {
+    if (!SetAndroidProperty("heapprofd.enable", "all"))
+      return Handle(nullptr);
+  }
+  alls_++;
+  return Handle(this);
+}
+
+SystemProperties::~SystemProperties() {
+  PERFETTO_DCHECK(alls_ == 0 && properties_.empty());
+}
+
+bool SystemProperties::SetAndroidProperty(const std::string& name,
+                                          const std::string& value) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  return __system_property_set(name.c_str(), value.c_str());
+#else
+  // Allow this to be mocked out for tests on other platforms.
+  base::ignore_result(name);
+  base::ignore_result(value);
+  PERFETTO_FATAL("Properties can only be set on Android.");
+#endif
+}
+
+void SystemProperties::UnsetProperty(const std::string& name) {
+  auto it = properties_.find(name);
+  if (it == properties_.end()) {
+    PERFETTO_DFATAL("Unsetting unknown property.");
+    return;
+  }
+  if (--(it->second) == 0) {
+    properties_.erase(it);
+    SetAndroidProperty("heapprofd.enable." + name, "");
+    if (properties_.empty() && alls_ == 0)
+      SetAndroidProperty("heapprofd.enable", "");
+  }
+}
+
+void SystemProperties::UnsetAll() {
+  if (--alls_ == 0) {
+    if (properties_.empty())
+      SetAndroidProperty("heapprofd.enable", "");
+    else
+      SetAndroidProperty("heapprofd.enable", "1");
+  }
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/system_property.h b/src/profiling/memory/system_property.h
new file mode 100644
index 0000000..5bb6e8f
--- /dev/null
+++ b/src/profiling/memory/system_property.h
@@ -0,0 +1,83 @@
+/*
+ * 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_PROFILING_MEMORY_SYSTEM_PROPERTY_H_
+#define SRC_PROFILING_MEMORY_SYSTEM_PROPERTY_H_
+
+#include <map>
+#include <string>
+
+namespace perfetto {
+namespace profiling {
+
+// SystemProperties allows to set properties in a reference counted fashion.
+// SetAll() is used to enable startup profiling for all programs, SetProperty
+// can be used to enable startup profiling for a specific program name.
+// Both of those return opaque Handles that need to be held on to as long as
+// startup profiling should be enabled.
+//
+// This automatically manages the heappprofd.enable flag, which is first
+// checked to determine whether to check the program name specific flag.
+// Once the last Handle for a given program name goes away, the flag for the
+// program name is unset. Once the last of all Handles goes away, the
+// heapprofd.enable flag is unset.
+// See
+// https://android.googlesource.com/platform/bionic/+/0dbe6d1aec12d2f30f0331dcfea6dc8e8c55cf97/libc/bionic/malloc_common.cpp#473
+class SystemProperties {
+ public:
+  class Handle {
+   public:
+    Handle(const Handle&) = delete;
+    Handle& operator=(const Handle&) = delete;
+
+    Handle(Handle&&);
+    Handle& operator=(Handle&&);
+
+    friend class SystemProperties;
+    ~Handle();
+    operator bool();
+
+   private:
+    explicit Handle(SystemProperties* system_properties, std::string property);
+    explicit Handle(SystemProperties* system_properties);
+
+    SystemProperties* system_properties_;
+    std::string property_;
+    bool all_ = false;
+  };
+
+  Handle SetProperty(std::string name);
+  Handle SetAll();
+
+  virtual ~SystemProperties();
+
+ protected:
+  // virtual for testing.
+  virtual bool SetAndroidProperty(const std::string& name,
+                                  const std::string& value);
+
+ private:
+  void UnsetProperty(const std::string& name);
+  void UnsetAll();
+
+  size_t alls_ = 0;
+  std::map<std::string, size_t> properties_;
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_SYSTEM_PROPERTY_H_
diff --git a/src/profiling/memory/system_property_unittest.cc b/src/profiling/memory/system_property_unittest.cc
new file mode 100644
index 0000000..2bf16ab
--- /dev/null
+++ b/src/profiling/memory/system_property_unittest.cc
@@ -0,0 +1,159 @@
+/*
+ * 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 "src/profiling/memory/system_property.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+using ::testing::InSequence;
+using ::testing::Return;
+
+class MockSystemProperties : public SystemProperties {
+ public:
+  MOCK_METHOD2(SetAndroidProperty,
+               bool(const std::string&, const std::string&));
+};
+
+TEST(SystemPropertyTest, All) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "all"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  auto handle = prop.SetAll();
+}
+
+TEST(SystemPropertyTest, RefcountAll) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "all"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  {
+    auto handle = prop.SetAll();
+    { auto handle2 = prop.SetAll(); }
+  }
+}
+
+TEST(SystemPropertyTest, CleanupAll) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "all"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  { auto handle = prop.SetAll(); }
+}
+
+TEST(SystemPropertyTest, Specific) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", ""))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  auto handle2 = prop.SetProperty("system_server");
+}
+
+TEST(SystemPropertyTest, RefcountSpecific) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", ""))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  {
+    auto handle = prop.SetProperty("system_server");
+    { auto handle2 = prop.SetProperty("system_server"); }
+  }
+}
+
+TEST(SystemPropertyTest, CleanupSpecific) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", ""))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  { auto handle2 = prop.SetProperty("system_server"); }
+}
+
+TEST(SystemPropertyTest, AllAndSpecific) {
+  MockSystemProperties prop;
+  InSequence s;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "all"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", ""))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", ""))
+      .WillOnce(Return(true));
+  auto handle = prop.SetAll();
+  auto handle2 = prop.SetProperty("system_server");
+  { SystemProperties::Handle destroy = std::move(handle); }
+}
+
+TEST(SystemPropertyTest, AllFailed) {
+  MockSystemProperties prop;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "all"))
+      .WillOnce(Return(false));
+  auto handle = prop.SetAll();
+  EXPECT_FALSE(handle);
+}
+
+TEST(SystemPropertyTest, SpecificFailed) {
+  MockSystemProperties prop;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", "1"))
+      .WillOnce(Return(false));
+  auto handle = prop.SetProperty("system_server");
+  EXPECT_FALSE(handle);
+}
+
+TEST(SystemPropertyTest, SpecificFailedMainProperty) {
+  MockSystemProperties prop;
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable.system_server", "1"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prop, SetAndroidProperty("heapprofd.enable", "1"))
+      .WillOnce(Return(false));
+  auto handle = prop.SetProperty("system_server");
+  EXPECT_FALSE(handle);
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto