Add DaydreamVR native libraries and services
Upstreaming the main VR system components from master-dreamos-dev
into goog/master.
Bug: None
Test: `m -j32` succeeds. Sailfish boots and basic_vr sample app works
Change-Id: I853015872afc443aecee10411ef2d6b79184d051
diff --git a/services/vr/performanced/Android.mk b/services/vr/performanced/Android.mk
new file mode 100644
index 0000000..6256e90
--- /dev/null
+++ b/services/vr/performanced/Android.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+ cpu_set.cpp \
+ main.cpp \
+ performance_service.cpp \
+ task.cpp
+
+staticLibraries := \
+ libperformance \
+ libpdx_default_transport \
+
+sharedLibraries := \
+ libbase \
+ libcutils \
+ liblog \
+ libutils
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_CFLAGS := -DLOG_TAG=\"performanced\"
+LOCAL_CFLAGS += -DTRACE=0
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := performanced
+LOCAL_INIT_RC := performanced.rc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := performance_service_tests.cpp
+LOCAL_STATIC_LIBRARIES := $(staticLibraries) libgtest_main
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := performance_service_tests
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_NATIVE_TEST)
diff --git a/services/vr/performanced/CPPLINT.cfg b/services/vr/performanced/CPPLINT.cfg
new file mode 100644
index 0000000..fd379da
--- /dev/null
+++ b/services/vr/performanced/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-runtime/int
diff --git a/services/vr/performanced/cpu_set.cpp b/services/vr/performanced/cpu_set.cpp
new file mode 100644
index 0000000..916226e
--- /dev/null
+++ b/services/vr/performanced/cpu_set.cpp
@@ -0,0 +1,287 @@
+#include "cpu_set.h"
+
+#include <cutils/log.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+
+#include "directory_reader.h"
+#include "stdio_filebuf.h"
+#include "task.h"
+#include "unique_file.h"
+
+namespace {
+
+constexpr int kDirectoryFlags = O_RDONLY | O_DIRECTORY | O_CLOEXEC;
+constexpr pid_t kKernelThreadDaemonPid = 2;
+
+} // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+bool CpuSet::prefix_enabled_ = false;
+
+void CpuSetManager::Load(const std::string& cpuset_root) {
+ if (!root_set_)
+ root_set_ = Create(cpuset_root);
+}
+
+std::unique_ptr<CpuSet> CpuSetManager::Create(const std::string& path) {
+ base::unique_fd root_cpuset_fd(open(path.c_str(), kDirectoryFlags));
+ if (root_cpuset_fd.get() < 0) {
+ ALOGE("CpuSet::Create: Failed to open \"%s\": %s", path.c_str(),
+ strerror(errno));
+ return nullptr;
+ }
+
+ return Create(std::move(root_cpuset_fd), "/", nullptr);
+}
+
+std::unique_ptr<CpuSet> CpuSetManager::Create(base::unique_fd base_fd,
+ const std::string& name,
+ CpuSet* parent) {
+ DirectoryReader directory(base::unique_fd(dup(base_fd)));
+ if (!directory) {
+ ALOGE("CpuSet::Create: Failed to opendir %s cpuset: %s", name.c_str(),
+ strerror(directory.GetError()));
+ return nullptr;
+ }
+
+ std::unique_ptr<CpuSet> group(
+ new CpuSet(parent, name, base::unique_fd(dup(base_fd))));
+ path_map_.insert(std::make_pair(group->path(), group.get()));
+
+ while (dirent* entry = directory.Next()) {
+ if (entry->d_type == DT_DIR) {
+ std::string directory_name(entry->d_name);
+
+ if (directory_name == "." || directory_name == "..")
+ continue;
+
+ base::unique_fd entry_fd(
+ openat(base_fd.get(), directory_name.c_str(), kDirectoryFlags));
+ if (entry_fd.get() >= 0) {
+ auto child =
+ Create(std::move(entry_fd), directory_name.c_str(), group.get());
+
+ if (child)
+ group->AddChild(std::move(child));
+ else
+ return nullptr;
+ } else {
+ ALOGE("CpuSet::Create: Failed to openat \"%s\": %s", entry->d_name,
+ strerror(errno));
+ return nullptr;
+ }
+ }
+ }
+
+ return group;
+}
+
+CpuSet* CpuSetManager::Lookup(const std::string& path) {
+ auto search = path_map_.find(path);
+ if (search != path_map_.end())
+ return search->second;
+ else
+ return nullptr;
+}
+
+std::vector<CpuSet*> CpuSetManager::GetCpuSets() {
+ std::vector<CpuSet*> sets(path_map_.size());
+
+ for (const auto& pair : path_map_) {
+ sets.push_back(pair.second);
+ }
+
+ return sets;
+}
+
+std::string CpuSetManager::DumpState() const {
+ size_t max_path = 0;
+ std::vector<CpuSet*> sets;
+
+ for (const auto& pair : path_map_) {
+ max_path = std::max(max_path, pair.second->path().length());
+ sets.push_back(pair.second);
+ }
+
+ std::sort(sets.begin(), sets.end(), [](const CpuSet* a, const CpuSet* b) {
+ return a->path() < b->path();
+ });
+
+ std::ostringstream stream;
+
+ stream << std::left;
+ stream << std::setw(max_path) << "Path";
+ stream << " ";
+ stream << std::setw(6) << "CPUs";
+ stream << " ";
+ stream << std::setw(6) << "Tasks";
+ stream << std::endl;
+
+ stream << std::string(max_path, '_');
+ stream << " ";
+ stream << std::string(6, '_');
+ stream << " ";
+ stream << std::string(6, '_');
+ stream << std::endl;
+
+ for (const auto set : sets) {
+ stream << std::left;
+ stream << std::setw(max_path) << set->path();
+ stream << " ";
+ stream << std::right;
+ stream << std::setw(6) << set->GetCpuList();
+ stream << " ";
+ stream << std::setw(6) << set->GetTasks().size();
+ stream << std::endl;
+ }
+
+ return stream.str();
+}
+
+void CpuSetManager::MoveUnboundTasks(const std::string& target_set) {
+ auto root = Lookup("/");
+ if (!root) {
+ ALOGE("CpuSetManager::MoveUnboundTasks: Failed to find root cpuset!");
+ return;
+ }
+
+ auto target = Lookup(target_set);
+ if (!target) {
+ ALOGE(
+ "CpuSetManager::MoveUnboundTasks: Failed to find target cpuset \"%s\"!",
+ target_set.c_str());
+ return;
+ }
+
+ auto cpu_list = root->GetCpuList();
+
+ for (auto task_id : root->GetTasks()) {
+ Task task(task_id);
+
+ // Move only unbound kernel threads to the target cpuset.
+ if (task.cpus_allowed_list() == cpu_list &&
+ task.parent_process_id() == kKernelThreadDaemonPid) {
+ ALOGD_IF(TRACE,
+ "CpuSetManager::MoveUnboundTasks: Moving task_id=%d name=%s to "
+ "target_set=%s tgid=%d ppid=%d.",
+ task_id, task.name().c_str(), target_set.c_str(),
+ task.thread_group_id(), task.parent_process_id());
+
+ const int ret = target->AttachTask(task_id);
+ ALOGW_IF(ret < 0 && ret != -EINVAL,
+ "CpuSetManager::MoveUnboundTasks: Failed to attach task_id=%d "
+ "to cpuset=%s: %s",
+ task_id, target_set.c_str(), strerror(-ret));
+ } else {
+ ALOGD_IF(TRACE,
+ "CpuSet::MoveUnboundTasks: Skipping task_id=%d name=%s cpus=%s.",
+ task_id, task.name().c_str(), task.cpus_allowed_list().c_str());
+ }
+ }
+}
+
+CpuSet::CpuSet(CpuSet* parent, const std::string& name,
+ base::unique_fd&& cpuset_fd)
+ : parent_(parent), name_(name), cpuset_fd_(std::move(cpuset_fd)) {
+ if (parent_ == nullptr)
+ path_ = name_;
+ else if (parent_->IsRoot())
+ path_ = parent_->name() + name_;
+ else
+ path_ = parent_->path() + "/" + name_;
+
+ ALOGI("CpuSet::CpuSet: path=%s", path().c_str());
+}
+
+base::unique_fd CpuSet::OpenPropertyFile(const std::string& name) const {
+ return OpenFile(prefix_enabled_ ? "cpuset." + name : name);
+}
+
+UniqueFile CpuSet::OpenPropertyFilePointer(const std::string& name) const {
+ return OpenFilePointer(prefix_enabled_ ? "cpuset." + name : name);
+}
+
+base::unique_fd CpuSet::OpenFile(const std::string& name, int flags) const {
+ const std::string relative_path = "./" + name;
+ return base::unique_fd(
+ openat(cpuset_fd_.get(), relative_path.c_str(), flags));
+}
+
+UniqueFile CpuSet::OpenFilePointer(const std::string& name, int flags) const {
+ const std::string relative_path = "./" + name;
+ base::unique_fd fd(openat(cpuset_fd_.get(), relative_path.c_str(), flags));
+ if (fd.get() < 0) {
+ ALOGE("CpuSet::OpenPropertyFilePointer: Failed to open %s/%s: %s",
+ path_.c_str(), name.c_str(), strerror(errno));
+ return nullptr;
+ }
+
+ UniqueFile fp(fdopen(fd.release(), "r"));
+ if (!fp)
+ ALOGE("CpuSet::OpenPropertyFilePointer: Failed to fdopen %s/%s: %s",
+ path_.c_str(), name.c_str(), strerror(errno));
+
+ return fp;
+}
+
+int CpuSet::AttachTask(pid_t task_id) const {
+ auto file = OpenFile("tasks", O_RDWR);
+ if (file.get() >= 0) {
+ std::ostringstream stream;
+ stream << task_id;
+ std::string value = stream.str();
+
+ const bool ret = base::WriteStringToFd(value, file.get());
+ return !ret ? -errno : 0;
+ } else {
+ ALOGE("CpuSet::AttachTask: Failed to open %s/tasks: %s", path_.c_str(),
+ strerror(errno));
+ return -errno;
+ }
+}
+
+std::vector<pid_t> CpuSet::GetTasks() const {
+ std::vector<pid_t> tasks;
+
+ if (auto file = OpenFilePointer("tasks")) {
+ stdio_filebuf<char> filebuf(file.get());
+ std::istream file_stream(&filebuf);
+
+ for (std::string line; std::getline(file_stream, line);) {
+ pid_t task_id = std::strtol(line.c_str(), nullptr, 10);
+ tasks.push_back(task_id);
+ }
+ }
+
+ return tasks;
+}
+
+std::string CpuSet::GetCpuList() const {
+ if (auto file = OpenPropertyFilePointer("cpus")) {
+ stdio_filebuf<char> filebuf(file.get());
+ std::istream file_stream(&filebuf);
+
+ std::string line;
+ if (std::getline(file_stream, line))
+ return line;
+ }
+
+ ALOGE("CpuSet::GetCpuList: Failed to read cpu list!!!");
+ return "";
+}
+
+void CpuSet::AddChild(std::unique_ptr<CpuSet> child) {
+ children_.push_back(std::move(child));
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/performanced/cpu_set.h b/services/vr/performanced/cpu_set.h
new file mode 100644
index 0000000..fcf280a
--- /dev/null
+++ b/services/vr/performanced/cpu_set.h
@@ -0,0 +1,105 @@
+#ifndef ANDROID_DVR_PERFORMANCED_CPU_SET_H_
+#define ANDROID_DVR_PERFORMANCED_CPU_SET_H_
+
+#include <fcntl.h>
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "unique_file.h"
+
+namespace android {
+namespace dvr {
+
+class CpuSet {
+ public:
+ // Returns the parent group for this group, if any. This pointer is owned by
+ // the group hierarchy and is only valid as long as the hierarchy is valid.
+ CpuSet* parent() const { return parent_; }
+ std::string name() const { return name_; }
+ std::string path() const { return path_; }
+
+ bool IsRoot() const { return parent_ == nullptr; }
+
+ std::string GetCpuList() const;
+
+ int AttachTask(pid_t task_id) const;
+ std::vector<pid_t> GetTasks() const;
+
+ private:
+ friend class CpuSetManager;
+
+ CpuSet(CpuSet* parent, const std::string& name, base::unique_fd&& cpuset_fd);
+
+ void AddChild(std::unique_ptr<CpuSet> child);
+
+ base::unique_fd OpenPropertyFile(const std::string& name) const;
+ UniqueFile OpenPropertyFilePointer(const std::string& name) const;
+
+ base::unique_fd OpenFile(const std::string& name, int flags = O_RDONLY) const;
+ UniqueFile OpenFilePointer(const std::string& name,
+ int flags = O_RDONLY) const;
+
+ CpuSet* parent_;
+ std::string name_;
+ std::string path_;
+ base::unique_fd cpuset_fd_;
+ std::vector<std::unique_ptr<CpuSet>> children_;
+
+ static void SetPrefixEnabled(bool enabled) { prefix_enabled_ = enabled; }
+ static bool prefix_enabled_;
+
+ CpuSet(const CpuSet&) = delete;
+ void operator=(const CpuSet&) = delete;
+};
+
+class CpuSetManager {
+ public:
+ CpuSetManager() {}
+
+ // Creats a CpuSet hierarchy by walking the directory tree starting at
+ // |cpuset_root|. This argument must be the path to the root cpuset for the
+ // system, which is usually /dev/cpuset.
+ void Load(const std::string& cpuset_root);
+
+ // Lookup and return a CpuSet from a cpuset path. Ownership of the pointer
+ // DOES NOT pass to the caller; the pointer remains valid as long as the
+ // CpuSet hierarchy is valid.
+ CpuSet* Lookup(const std::string& path);
+
+ // Returns a vector of all the cpusets found at initializaiton. Ownership of
+ // the pointers to CpuSets DOES NOT pass to the caller; the pointers remain
+ // valid as long as the CpuSet hierarchy is valid.
+ std::vector<CpuSet*> GetCpuSets();
+
+ // Moves all unbound tasks from the root set into the target set. This is used
+ // to shield the system from interference from unbound kernel threads.
+ void MoveUnboundTasks(const std::string& target_set);
+
+ std::string DumpState() const;
+
+ operator bool() const { return root_set_ != nullptr; }
+
+ private:
+ // Creates a CpuSet from a path to a cpuset cgroup directory. Recursively
+ // creates child groups for each directory found under |path|.
+ std::unique_ptr<CpuSet> Create(const std::string& path);
+ std::unique_ptr<CpuSet> Create(base::unique_fd base_fd,
+ const std::string& name, CpuSet* parent);
+
+ std::unique_ptr<CpuSet> root_set_;
+ std::unordered_map<std::string, CpuSet*> path_map_;
+
+ CpuSetManager(const CpuSetManager&) = delete;
+ void operator=(const CpuSetManager&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_CPU_SET_H_
diff --git a/services/vr/performanced/directory_reader.h b/services/vr/performanced/directory_reader.h
new file mode 100644
index 0000000..7d7ecc5
--- /dev/null
+++ b/services/vr/performanced/directory_reader.h
@@ -0,0 +1,55 @@
+#ifndef ANDROID_DVR_PERFORMANCED_DIRECTORY_READER_H_
+#define ANDROID_DVR_PERFORMANCED_DIRECTORY_READER_H_
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace dvr {
+
+// Utility class around readdir() that handles automatic cleanup.
+class DirectoryReader {
+ public:
+ explicit DirectoryReader(base::unique_fd directory_fd) {
+ directory_ = fdopendir(directory_fd.get());
+ error_ = errno;
+ if (directory_ != nullptr)
+ directory_fd.release();
+ }
+
+ ~DirectoryReader() {
+ if (directory_)
+ closedir(directory_);
+ }
+
+ bool IsValid() const { return directory_ != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+ int GetError() const { return error_; }
+
+ // Returns a pointer to a dirent describing the next directory entry. The
+ // pointer is only valid unitl the next call to Next() or the DirectoryReader
+ // is destroyed. Returns nullptr when the end of the directory is reached.
+ dirent* Next() {
+ if (directory_)
+ return readdir(directory_);
+ else
+ return nullptr;
+ }
+
+ private:
+ DIR* directory_;
+ int error_;
+
+ DirectoryReader(const DirectoryReader&) = delete;
+ void operator=(const DirectoryReader&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_DIRECTORY_READER_H_
diff --git a/services/vr/performanced/main.cpp b/services/vr/performanced/main.cpp
new file mode 100644
index 0000000..114413d
--- /dev/null
+++ b/services/vr/performanced/main.cpp
@@ -0,0 +1,76 @@
+#include <errno.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <cutils/sched_policy.h>
+#include <sys/resource.h>
+#include <utils/threads.h>
+
+#include <pdx/default_transport/service_dispatcher.h>
+#include <private/android_filesystem_config.h>
+
+#include "performance_service.h"
+
+namespace {
+
+// Annoying that sys/capability.h doesn't define this directly.
+constexpr int kMaxCapNumber = (CAP_TO_INDEX(CAP_LAST_CAP) + 1);
+
+} // anonymous namespace
+
+int main(int /*argc*/, char** /*argv*/) {
+ int ret = -1;
+
+ struct __user_cap_header_struct capheader;
+ struct __user_cap_data_struct capdata[kMaxCapNumber];
+
+ std::shared_ptr<android::pdx::Service> service;
+ std::unique_ptr<android::pdx::ServiceDispatcher> dispatcher;
+
+ ALOGI("Starting up...");
+
+ // We need to be able to create endpoints with full perms.
+ umask(0000);
+
+ // Keep capabilities when switching UID to AID_SYSTEM.
+ ret = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
+ CHECK_ERROR(ret < 0, error, "Failed to set KEEPCAPS: %s", strerror(errno));
+
+ // Set UID and GID to system.
+ ret = setresgid(AID_SYSTEM, AID_SYSTEM, AID_SYSTEM);
+ CHECK_ERROR(ret < 0, error, "Failed to set GID: %s", strerror(errno));
+ ret = setresuid(AID_SYSTEM, AID_SYSTEM, AID_SYSTEM);
+ CHECK_ERROR(ret < 0, error, "Failed to set UID: %s", strerror(errno));
+
+ // Keep CAP_SYS_NICE, allowing control of scheduler class, priority, and
+ // cpuset for other tasks in the system.
+ memset(&capheader, 0, sizeof(capheader));
+ memset(&capdata, 0, sizeof(capdata));
+ capheader.version = _LINUX_CAPABILITY_VERSION_3;
+ capdata[CAP_TO_INDEX(CAP_SYS_NICE)].effective |= CAP_TO_MASK(CAP_SYS_NICE);
+ capdata[CAP_TO_INDEX(CAP_SYS_NICE)].permitted |= CAP_TO_MASK(CAP_SYS_NICE);
+
+ // Drop all caps but the ones configured above.
+ ret = capset(&capheader, capdata);
+ CHECK_ERROR(ret < 0, error, "Could not set capabilities: %s",
+ strerror(errno));
+
+ dispatcher = android::pdx::default_transport::ServiceDispatcher::Create();
+ CHECK_ERROR(!dispatcher, error, "Failed to create service dispatcher.");
+
+ service = android::dvr::PerformanceService::Create();
+ CHECK_ERROR(!service, error, "Failed to create performance service service.");
+ dispatcher->AddService(service);
+
+ ALOGI("Entering message loop.");
+
+ ret = dispatcher->EnterDispatchLoop();
+ CHECK_ERROR(ret < 0, error, "Dispatch loop exited because: %s\n",
+ strerror(-ret));
+
+error:
+ return ret;
+}
diff --git a/services/vr/performanced/performance_service.cpp b/services/vr/performanced/performance_service.cpp
new file mode 100644
index 0000000..c99c8d4
--- /dev/null
+++ b/services/vr/performanced/performance_service.cpp
@@ -0,0 +1,196 @@
+#include "performance_service.h"
+
+#include <sched.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include <pdx/default_transport/service_endpoint.h>
+#include <pdx/rpc/argument_encoder.h>
+#include <pdx/rpc/message_buffer.h>
+#include <pdx/rpc/remote_method.h>
+#include <private/dvr/performance_rpc.h>
+
+#include "task.h"
+
+// This prctl is only available in Android kernels.
+#define PR_SET_TIMERSLACK_PID 41
+
+using android::pdx::Message;
+using android::pdx::rpc::DispatchRemoteMethod;
+using android::pdx::default_transport::Endpoint;
+
+namespace {
+
+const char kCpuSetBasePath[] = "/dev/cpuset";
+
+constexpr unsigned long kTimerSlackForegroundNs = 50000;
+constexpr unsigned long kTimerSlackBackgroundNs = 40000000;
+
+} // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+PerformanceService::PerformanceService()
+ : BASE("PerformanceService",
+ Endpoint::Create(PerformanceRPC::kClientPath)) {
+ cpuset_.Load(kCpuSetBasePath);
+
+ Task task(getpid());
+ ALOGI("Running in cpuset=%s uid=%d gid=%d", task.GetCpuSetPath().c_str(),
+ task.user_id()[Task::kUidReal], task.group_id()[Task::kUidReal]);
+
+ // Errors here are checked in IsInitialized().
+ sched_fifo_min_priority_ = sched_get_priority_min(SCHED_FIFO);
+ sched_fifo_max_priority_ = sched_get_priority_max(SCHED_FIFO);
+
+ const int fifo_range = sched_fifo_max_priority_ - sched_fifo_min_priority_;
+ const int fifo_low = sched_fifo_min_priority_;
+ const int fifo_medium = sched_fifo_min_priority_ + fifo_range / 5;
+
+ // TODO(eieio): Make this configurable on the command line.
+ cpuset_.MoveUnboundTasks("/kernel");
+
+ // Setup the scheduler classes.
+ scheduler_classes_ = {
+ {"audio:low",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_medium}},
+ {"audio:high",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_medium + 3}},
+ {"graphics",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_medium}},
+ {"graphics:low",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_medium}},
+ {"graphics:high",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_medium + 2}},
+ {"sensors",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_low}},
+ {"sensors:low",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_low}},
+ {"sensors:high",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+ .priority = fifo_low + 1}},
+ {"normal",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_NORMAL,
+ .priority = 0}},
+ {"foreground",
+ {.timer_slack = kTimerSlackForegroundNs,
+ .scheduler_policy = SCHED_NORMAL,
+ .priority = 0}},
+ {"background",
+ {.timer_slack = kTimerSlackBackgroundNs,
+ .scheduler_policy = SCHED_BATCH,
+ .priority = 0}},
+ {"batch",
+ {.timer_slack = kTimerSlackBackgroundNs,
+ .scheduler_policy = SCHED_BATCH,
+ .priority = 0}},
+ };
+}
+
+bool PerformanceService::IsInitialized() const {
+ return BASE::IsInitialized() && cpuset_ && sched_fifo_min_priority_ >= 0 &&
+ sched_fifo_max_priority_ >= 0;
+}
+
+std::string PerformanceService::DumpState(size_t /*max_length*/) {
+ return cpuset_.DumpState();
+}
+
+int PerformanceService::OnSetCpuPartition(Message& message, pid_t task_id,
+ const std::string& partition) {
+ Task task(task_id);
+ if (!task || task.thread_group_id() != message.GetProcessId())
+ return -EINVAL;
+
+ auto target_set = cpuset_.Lookup(partition);
+ if (!target_set)
+ return -ENOENT;
+
+ const auto attach_error = target_set->AttachTask(task_id);
+ if (attach_error)
+ return attach_error;
+
+ return 0;
+}
+
+int PerformanceService::OnSetSchedulerClass(
+ Message& message, pid_t task_id, const std::string& scheduler_class) {
+ // Make sure the task id is valid and belongs to the sending process.
+ Task task(task_id);
+ if (!task || task.thread_group_id() != message.GetProcessId())
+ return -EINVAL;
+
+ struct sched_param param;
+
+ // TODO(eieio): Apply rules based on the requesting process. Applications are
+ // only allowed one audio thread that runs at SCHED_FIFO. System services can
+ // have more than one.
+ auto search = scheduler_classes_.find(scheduler_class);
+ if (search != scheduler_classes_.end()) {
+ auto config = search->second;
+ param.sched_priority = config.priority;
+ sched_setscheduler(task_id, config.scheduler_policy, ¶m);
+ prctl(PR_SET_TIMERSLACK_PID, config.timer_slack, task_id);
+ ALOGI("PerformanceService::OnSetSchedulerClass: Set task=%d to class=%s.",
+ task_id, scheduler_class.c_str());
+ return 0;
+ } else {
+ ALOGE(
+ "PerformanceService::OnSetSchedulerClass: Invalid class=%s requested "
+ "by task=%d.",
+ scheduler_class.c_str(), task_id);
+ return -EINVAL;
+ }
+}
+
+std::string PerformanceService::OnGetCpuPartition(Message& message,
+ pid_t task_id) {
+ // Make sure the task id is valid and belongs to the sending process.
+ Task task(task_id);
+ if (!task || task.thread_group_id() != message.GetProcessId())
+ REPLY_ERROR_RETURN(message, EINVAL, "");
+
+ return task.GetCpuSetPath();
+}
+
+int PerformanceService::HandleMessage(Message& message) {
+ switch (message.GetOp()) {
+ case PerformanceRPC::SetCpuPartition::Opcode:
+ DispatchRemoteMethod<PerformanceRPC::SetSchedulerClass>(
+ *this, &PerformanceService::OnSetCpuPartition, message);
+ return 0;
+
+ case PerformanceRPC::SetSchedulerClass::Opcode:
+ DispatchRemoteMethod<PerformanceRPC::SetSchedulerClass>(
+ *this, &PerformanceService::OnSetSchedulerClass, message);
+ return 0;
+
+ case PerformanceRPC::GetCpuPartition::Opcode:
+ DispatchRemoteMethod<PerformanceRPC::GetCpuPartition>(
+ *this, &PerformanceService::OnGetCpuPartition, message);
+ return 0;
+
+ default:
+ return Service::HandleMessage(message);
+ }
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/performanced/performance_service.h b/services/vr/performanced/performance_service.h
new file mode 100644
index 0000000..e32d834
--- /dev/null
+++ b/services/vr/performanced/performance_service.h
@@ -0,0 +1,57 @@
+#ifndef ANDROID_DVR_PERFORMANCED_PERFORMANCE_SERVICE_H_
+#define ANDROID_DVR_PERFORMANCED_PERFORMANCE_SERVICE_H_
+
+#include <string>
+#include <unordered_map>
+
+#include <pdx/service.h>
+
+#include "cpu_set.h"
+
+namespace android {
+namespace dvr {
+
+// PerformanceService manages compute partitions usings cpusets. Different
+// cpusets are assigned specific purposes and performance characteristics;
+// clients may request for threads to be moved into these cpusets to help
+// achieve system performance goals.
+class PerformanceService : public pdx::ServiceBase<PerformanceService> {
+ public:
+ int HandleMessage(pdx::Message& message) override;
+ bool IsInitialized() const override;
+
+ std::string DumpState(size_t max_length) override;
+
+ private:
+ friend BASE;
+
+ PerformanceService();
+
+ int OnSetCpuPartition(pdx::Message& message, pid_t task_id,
+ const std::string& partition);
+ int OnSetSchedulerClass(pdx::Message& message, pid_t task_id,
+ const std::string& scheduler_class);
+ std::string OnGetCpuPartition(pdx::Message& message, pid_t task_id);
+
+ CpuSetManager cpuset_;
+
+ int sched_fifo_min_priority_;
+ int sched_fifo_max_priority_;
+
+ // Scheduler class config type.
+ struct SchedulerClassConfig {
+ unsigned long timer_slack;
+ int scheduler_policy;
+ int priority;
+ };
+
+ std::unordered_map<std::string, SchedulerClassConfig> scheduler_classes_;
+
+ PerformanceService(const PerformanceService&) = delete;
+ void operator=(const PerformanceService&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_PERFORMANCE_SERVICE_H_
diff --git a/services/vr/performanced/performance_service_tests.cpp b/services/vr/performanced/performance_service_tests.cpp
new file mode 100644
index 0000000..b526082
--- /dev/null
+++ b/services/vr/performanced/performance_service_tests.cpp
@@ -0,0 +1,137 @@
+#include <errno.h>
+#include <sched.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+#include <dvr/performance_client_api.h>
+#include <gtest/gtest.h>
+
+TEST(DISABLED_PerformanceTest, SetCpuPartition) {
+ int error;
+
+ // Test setting the the partition for the current task.
+ error = dvrSetCpuPartition(0, "/application/background");
+ EXPECT_EQ(0, error);
+
+ error = dvrSetCpuPartition(0, "/application/performance");
+ EXPECT_EQ(0, error);
+
+ // Test setting the partition for one of our tasks.
+ bool done = false;
+ pid_t task_id = 0;
+ std::mutex mutex;
+ std::condition_variable done_condition, id_condition;
+
+ std::thread thread([&] {
+ std::unique_lock<std::mutex> lock(mutex);
+
+ task_id = gettid();
+ id_condition.notify_one();
+
+ done_condition.wait(lock, [&done] { return done; });
+ });
+
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ id_condition.wait(lock, [&task_id] { return task_id != 0; });
+ }
+ EXPECT_NE(0, task_id);
+
+ error = dvrSetCpuPartition(task_id, "/application");
+ EXPECT_EQ(0, error);
+
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ done = true;
+ done_condition.notify_one();
+ }
+ thread.join();
+
+ // Test setting the partition for a task that isn't valid using
+ // the task id of the thread that we just joined. Technically the
+ // id could wrap around by the time we get here, but this is
+ // extremely unlikely.
+ error = dvrSetCpuPartition(task_id, "/application");
+ EXPECT_EQ(-EINVAL, error);
+
+ // Test setting the partition for a task that doesn't belong to us.
+ error = dvrSetCpuPartition(1, "/application");
+ EXPECT_EQ(-EINVAL, error);
+
+ // Test setting the partition to one that doesn't exist.
+ error = dvrSetCpuPartition(0, "/foobar");
+ EXPECT_EQ(-ENOENT, error);
+}
+
+TEST(PerformanceTest, SetSchedulerClass) {
+ int error;
+
+ // TODO(eieio): Test all supported scheduler classes and priority levels.
+
+ error = dvrSetSchedulerClass(0, "background");
+ EXPECT_EQ(0, error);
+ EXPECT_EQ(SCHED_BATCH, sched_getscheduler(0));
+
+ error = dvrSetSchedulerClass(0, "audio:low");
+ EXPECT_EQ(0, error);
+ EXPECT_EQ(SCHED_FIFO | SCHED_RESET_ON_FORK, sched_getscheduler(0));
+
+ error = dvrSetSchedulerClass(0, "normal");
+ EXPECT_EQ(0, error);
+ EXPECT_EQ(SCHED_NORMAL, sched_getscheduler(0));
+
+ error = dvrSetSchedulerClass(0, "foobar");
+ EXPECT_EQ(-EINVAL, error);
+}
+
+TEST(PerformanceTest, SchedulerClassResetOnFork) {
+ int error;
+
+ error = dvrSetSchedulerClass(0, "graphics:high");
+ EXPECT_EQ(0, error);
+ EXPECT_EQ(SCHED_FIFO | SCHED_RESET_ON_FORK, sched_getscheduler(0));
+
+ int scheduler = -1;
+ std::thread thread([&]() { scheduler = sched_getscheduler(0); });
+ thread.join();
+
+ EXPECT_EQ(SCHED_NORMAL, scheduler);
+
+ // Return to SCHED_NORMAL.
+ error = dvrSetSchedulerClass(0, "normal");
+ EXPECT_EQ(0, error);
+ EXPECT_EQ(SCHED_NORMAL, sched_getscheduler(0));
+}
+
+TEST(PerformanceTest, GetCpuPartition) {
+ int error;
+ char partition[PATH_MAX + 1];
+
+ error = dvrSetCpuPartition(0, "/");
+ ASSERT_EQ(0, error);
+
+ error = dvrGetCpuPartition(0, partition, sizeof(partition));
+ EXPECT_EQ(0, error);
+ EXPECT_EQ("/", std::string(partition));
+
+ error = dvrSetCpuPartition(0, "/application");
+ EXPECT_EQ(0, error);
+
+ error = dvrGetCpuPartition(0, partition, sizeof(partition));
+ EXPECT_EQ(0, error);
+ EXPECT_EQ("/application", std::string(partition));
+
+ // Test passing a buffer that is too short.
+ error = dvrGetCpuPartition(0, partition, 5);
+ EXPECT_EQ(-ENOBUFS, error);
+
+ // Test getting the partition for a task that doesn't belong to us.
+ error = dvrGetCpuPartition(1, partition, sizeof(partition));
+ EXPECT_EQ(-EINVAL, error);
+
+ // Test passing a nullptr value for partition buffer.
+ error = dvrGetCpuPartition(0, nullptr, sizeof(partition));
+ EXPECT_EQ(-EINVAL, error);
+}
diff --git a/services/vr/performanced/performanced.rc b/services/vr/performanced/performanced.rc
new file mode 100644
index 0000000..754c97f
--- /dev/null
+++ b/services/vr/performanced/performanced.rc
@@ -0,0 +1,5 @@
+service performanced /system/bin/performanced
+ class core
+ user root
+ group system readproc
+ cpuset /
diff --git a/services/vr/performanced/stdio_filebuf.h b/services/vr/performanced/stdio_filebuf.h
new file mode 100644
index 0000000..5988aa8
--- /dev/null
+++ b/services/vr/performanced/stdio_filebuf.h
@@ -0,0 +1,219 @@
+// Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT
+// Copyright (c) 2016 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef ANDROID_DVR_PERFORMANCED_STDIO_FILEBUF_H_
+#define ANDROID_DVR_PERFORMANCED_STDIO_FILEBUF_H_
+
+#include <cstdio>
+#include <istream>
+#include <locale>
+#include <streambuf>
+
+namespace android {
+namespace dvr {
+
+// An implementation of std::basic_streambuf backed by a FILE pointer. This is
+// ported from the internal llvm-libc++ support for std::cin. It's really
+// unfortunate that we have to do this, but the C++11 standard is too pendantic
+// to support creating streams from file descriptors or FILE pointers. This
+// implementation uses all standard interfaces, except for the call to
+// std::__throw_runtime_error(), which is only needed to deal with exceeding
+// locale encoding limits. This class is meant to be used for reading system
+// files, which don't require exotic locale support, so this call could be
+// removed in the future, if necessary.
+//
+// Original source file: llvm-libcxx/llvm-libc++/include/__std_stream
+// Original class name: __stdinbuf
+//
+template <class _CharT>
+class stdio_filebuf
+ : public std::basic_streambuf<_CharT, std::char_traits<_CharT> > {
+ public:
+ typedef _CharT char_type;
+ typedef std::char_traits<char_type> traits_type;
+ typedef typename traits_type::int_type int_type;
+ typedef typename traits_type::pos_type pos_type;
+ typedef typename traits_type::off_type off_type;
+ typedef typename traits_type::state_type state_type;
+
+ explicit stdio_filebuf(FILE* __fp);
+ ~stdio_filebuf() override;
+
+ protected:
+ virtual int_type underflow() override;
+ virtual int_type uflow() override;
+ virtual int_type pbackfail(int_type __c = traits_type::eof()) override;
+ virtual void imbue(const std::locale& __loc) override;
+
+ private:
+ FILE* __file_;
+ const std::codecvt<char_type, char, state_type>* __cv_;
+ state_type __st_;
+ int __encoding_;
+ int_type __last_consumed_;
+ bool __last_consumed_is_next_;
+ bool __always_noconv_;
+
+ stdio_filebuf(const stdio_filebuf&);
+ stdio_filebuf& operator=(const stdio_filebuf&);
+
+ int_type __getchar(bool __consume);
+
+ static const int __limit = 8;
+};
+
+template <class _CharT>
+stdio_filebuf<_CharT>::stdio_filebuf(FILE* __fp)
+ : __file_(__fp),
+ __last_consumed_(traits_type::eof()),
+ __last_consumed_is_next_(false) {
+ imbue(this->getloc());
+}
+
+template <class _CharT>
+stdio_filebuf<_CharT>::~stdio_filebuf() {
+ if (__file_)
+ fclose(__file_);
+}
+
+template <class _CharT>
+void stdio_filebuf<_CharT>::imbue(const std::locale& __loc) {
+ __cv_ = &std::use_facet<std::codecvt<char_type, char, state_type> >(__loc);
+ __encoding_ = __cv_->encoding();
+ __always_noconv_ = __cv_->always_noconv();
+ if (__encoding_ > __limit)
+ std::__throw_runtime_error("unsupported locale for standard io");
+}
+
+template <class _CharT>
+typename stdio_filebuf<_CharT>::int_type stdio_filebuf<_CharT>::underflow() {
+ return __getchar(false);
+}
+
+template <class _CharT>
+typename stdio_filebuf<_CharT>::int_type stdio_filebuf<_CharT>::uflow() {
+ return __getchar(true);
+}
+
+template <class _CharT>
+typename stdio_filebuf<_CharT>::int_type stdio_filebuf<_CharT>::__getchar(
+ bool __consume) {
+ if (__last_consumed_is_next_) {
+ int_type __result = __last_consumed_;
+ if (__consume) {
+ __last_consumed_ = traits_type::eof();
+ __last_consumed_is_next_ = false;
+ }
+ return __result;
+ }
+ char __extbuf[__limit];
+ int __nread = std::max(1, __encoding_);
+ for (int __i = 0; __i < __nread; ++__i) {
+ int __c = getc(__file_);
+ if (__c == EOF)
+ return traits_type::eof();
+ __extbuf[__i] = static_cast<char>(__c);
+ }
+ char_type __1buf;
+ if (__always_noconv_)
+ __1buf = static_cast<char_type>(__extbuf[0]);
+ else {
+ const char* __enxt;
+ char_type* __inxt;
+ std::codecvt_base::result __r;
+ do {
+ state_type __sv_st = __st_;
+ __r = __cv_->in(__st_, __extbuf, __extbuf + __nread, __enxt, &__1buf,
+ &__1buf + 1, __inxt);
+ switch (__r) {
+ case std::codecvt_base::ok:
+ break;
+ case std::codecvt_base::partial:
+ __st_ = __sv_st;
+ if (__nread == sizeof(__extbuf))
+ return traits_type::eof();
+ {
+ int __c = getc(__file_);
+ if (__c == EOF)
+ return traits_type::eof();
+ __extbuf[__nread] = static_cast<char>(__c);
+ }
+ ++__nread;
+ break;
+ case std::codecvt_base::error:
+ return traits_type::eof();
+ case std::codecvt_base::noconv:
+ __1buf = static_cast<char_type>(__extbuf[0]);
+ break;
+ }
+ } while (__r == std::codecvt_base::partial);
+ }
+ if (!__consume) {
+ for (int __i = __nread; __i > 0;) {
+ if (ungetc(traits_type::to_int_type(__extbuf[--__i]), __file_) == EOF)
+ return traits_type::eof();
+ }
+ } else
+ __last_consumed_ = traits_type::to_int_type(__1buf);
+ return traits_type::to_int_type(__1buf);
+}
+
+template <class _CharT>
+typename stdio_filebuf<_CharT>::int_type stdio_filebuf<_CharT>::pbackfail(
+ int_type __c) {
+ if (traits_type::eq_int_type(__c, traits_type::eof())) {
+ if (!__last_consumed_is_next_) {
+ __c = __last_consumed_;
+ __last_consumed_is_next_ =
+ !traits_type::eq_int_type(__last_consumed_, traits_type::eof());
+ }
+ return __c;
+ }
+ if (__last_consumed_is_next_) {
+ char __extbuf[__limit];
+ char* __enxt;
+ const char_type __ci = traits_type::to_char_type(__last_consumed_);
+ const char_type* __inxt;
+ switch (__cv_->out(__st_, &__ci, &__ci + 1, __inxt, __extbuf,
+ __extbuf + sizeof(__extbuf), __enxt)) {
+ case std::codecvt_base::ok:
+ break;
+ case std::codecvt_base::noconv:
+ __extbuf[0] = static_cast<char>(__last_consumed_);
+ __enxt = __extbuf + 1;
+ break;
+ case std::codecvt_base::partial:
+ case std::codecvt_base::error:
+ return traits_type::eof();
+ }
+ while (__enxt > __extbuf)
+ if (ungetc(*--__enxt, __file_) == EOF)
+ return traits_type::eof();
+ }
+ __last_consumed_ = __c;
+ __last_consumed_is_next_ = true;
+ return __c;
+}
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_STDIO_FILEBUF_H_
diff --git a/services/vr/performanced/string_trim.h b/services/vr/performanced/string_trim.h
new file mode 100644
index 0000000..7094e9f
--- /dev/null
+++ b/services/vr/performanced/string_trim.h
@@ -0,0 +1,46 @@
+#ifndef ANDROID_DVR_PERFORMANCED_STRING_TRIM_H_
+#define ANDROID_DVR_PERFORMANCED_STRING_TRIM_H_
+
+#include <functional>
+#include <locale>
+#include <string>
+
+namespace android {
+namespace dvr {
+
+// Trims whitespace from the left side of |subject| and returns the result as a
+// new string.
+inline std::string LeftTrim(std::string subject) {
+ subject.erase(subject.begin(),
+ std::find_if(subject.begin(), subject.end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+ return subject;
+}
+
+// Trims whitespace from the right side of |subject| and returns the result as a
+// new string.
+inline std::string RightTrim(std::string subject) {
+ subject.erase(std::find_if(subject.rbegin(), subject.rend(),
+ std::not1(std::ptr_fun<int, int>(std::isspace)))
+ .base(),
+ subject.end());
+ return subject;
+}
+
+// Trims whitespace from the both sides of |subject| and returns the result as a
+// new string.
+inline std::string Trim(std::string subject) {
+ subject.erase(subject.begin(),
+ std::find_if(subject.begin(), subject.end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+ subject.erase(std::find_if(subject.rbegin(), subject.rend(),
+ std::not1(std::ptr_fun<int, int>(std::isspace)))
+ .base(),
+ subject.end());
+ return subject;
+}
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_STRING_TRIM_H_
diff --git a/services/vr/performanced/task.cpp b/services/vr/performanced/task.cpp
new file mode 100644
index 0000000..ad12858
--- /dev/null
+++ b/services/vr/performanced/task.cpp
@@ -0,0 +1,163 @@
+#include "task.h"
+
+#include <cutils/log.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <cctype>
+#include <cstdlib>
+#include <memory>
+#include <sstream>
+
+#include <android-base/unique_fd.h>
+
+#include "stdio_filebuf.h"
+#include "string_trim.h"
+
+namespace {
+
+const char kProcBase[] = "/proc";
+
+android::base::unique_fd OpenTaskDirectory(pid_t task_id) {
+ std::ostringstream stream;
+ stream << kProcBase << "/" << task_id;
+
+ return android::base::unique_fd(
+ open(stream.str().c_str(), O_RDONLY | O_DIRECTORY));
+}
+
+void ParseUidStatusField(const std::string& value, std::array<int, 4>& ids) {
+ const char* start = value.c_str();
+
+ ids[0] = std::strtol(start, const_cast<char**>(&start), 10);
+ ids[1] = std::strtol(start, const_cast<char**>(&start), 10);
+ ids[2] = std::strtol(start, const_cast<char**>(&start), 10);
+ ids[3] = std::strtol(start, const_cast<char**>(&start), 10);
+}
+
+} // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+Task::Task(pid_t task_id)
+ : task_id_(task_id),
+ thread_group_id_(-1),
+ parent_process_id_(-1),
+ thread_count_(0),
+ cpus_allowed_mask_(0) {
+ task_fd_ = OpenTaskDirectory(task_id_);
+ ALOGE_IF(task_fd_.get() < 0,
+ "Task::Task: Failed to open task directory for task_id=%d: %s",
+ task_id, strerror(errno));
+
+ ReadStatusFields();
+
+ ALOGD_IF(TRACE, "Task::Task: task_id=%d name=%s tgid=%d ppid=%d cpu_mask=%x",
+ task_id_, name_.c_str(), thread_group_id_, parent_process_id_,
+ cpus_allowed_mask_);
+}
+
+base::unique_fd Task::OpenTaskFile(const std::string& name) const {
+ const std::string relative_path = "./" + name;
+ return base::unique_fd(
+ openat(task_fd_.get(), relative_path.c_str(), O_RDONLY));
+}
+
+UniqueFile Task::OpenTaskFilePointer(const std::string& name) const {
+ const std::string relative_path = "./" + name;
+ base::unique_fd fd(openat(task_fd_.get(), relative_path.c_str(), O_RDONLY));
+ if (fd.get() < 0) {
+ ALOGE("Task::OpenTaskFilePointer: Failed to open /proc/%d/%s: %s", task_id_,
+ name.c_str(), strerror(errno));
+ return nullptr;
+ }
+
+ UniqueFile fp(fdopen(fd.release(), "r"));
+ if (!fp)
+ ALOGE("Task::OpenTaskFilePointer: Failed to fdopen /proc/%d/%s: %s",
+ task_id_, name.c_str(), strerror(errno));
+
+ return fp;
+}
+
+std::string Task::GetStatusField(const std::string& field) const {
+ if (auto file = OpenTaskFilePointer("status")) {
+ stdio_filebuf<char> filebuf(file.get());
+ std::istream file_stream(&filebuf);
+
+ for (std::string line; std::getline(file_stream, line);) {
+ auto offset = line.find(field);
+
+ ALOGD_IF(TRACE,
+ "Task::GetStatusField: field=\"%s\" line=\"%s\" offset=%zd",
+ field.c_str(), line.c_str(), offset);
+
+ if (offset == std::string::npos)
+ continue;
+
+ // The status file has lines with the format <field>:<value>. Extract the
+ // value after the colon.
+ return Trim(line.substr(offset + field.size() + 1));
+ }
+ }
+
+ return "[unknown]";
+}
+
+void Task::ReadStatusFields() {
+ if (auto file = OpenTaskFilePointer("status")) {
+ stdio_filebuf<char> filebuf(file.get());
+ std::istream file_stream(&filebuf);
+
+ for (std::string line; std::getline(file_stream, line);) {
+ auto offset = line.find(":");
+ if (offset == std::string::npos) {
+ ALOGW("ReadStatusFields: Failed to find delimiter \":\" in line=\"%s\"",
+ line.c_str());
+ continue;
+ }
+
+ std::string key = line.substr(0, offset);
+ std::string value = Trim(line.substr(offset + 1));
+
+ ALOGD_IF(TRACE, "Task::ReadStatusFields: key=\"%s\" value=\"%s\"",
+ key.c_str(), value.c_str());
+
+ if (key == "Name")
+ name_ = value;
+ else if (key == "Tgid")
+ thread_group_id_ = std::strtol(value.c_str(), nullptr, 10);
+ else if (key == "PPid")
+ parent_process_id_ = std::strtol(value.c_str(), nullptr, 10);
+ else if (key == "Uid")
+ ParseUidStatusField(value, user_id_);
+ else if (key == "Gid")
+ ParseUidStatusField(value, group_id_);
+ else if (key == "Threads")
+ thread_count_ = std::strtoul(value.c_str(), nullptr, 10);
+ else if (key == "Cpus_allowed")
+ cpus_allowed_mask_ = std::strtoul(value.c_str(), nullptr, 16);
+ else if (key == "Cpus_allowed_list")
+ cpus_allowed_list_ = value;
+ }
+ }
+}
+
+std::string Task::GetCpuSetPath() const {
+ if (auto file = OpenTaskFilePointer("cpuset")) {
+ stdio_filebuf<char> filebuf(file.get());
+ std::istream file_stream(&filebuf);
+
+ std::string line = "";
+ std::getline(file_stream, line);
+
+ return Trim(line);
+ } else {
+ return "";
+ }
+}
+
+} // namespace dvr
+} // namespace android
diff --git a/services/vr/performanced/task.h b/services/vr/performanced/task.h
new file mode 100644
index 0000000..4a3b7f2
--- /dev/null
+++ b/services/vr/performanced/task.h
@@ -0,0 +1,83 @@
+#ifndef ANDROID_DVR_PERFORMANCED_TASK_H_
+#define ANDROID_DVR_PERFORMANCED_TASK_H_
+
+#include <sys/types.h>
+
+#include <array>
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "unique_file.h"
+
+namespace android {
+namespace dvr {
+
+// Task provides access to task-related information from the procfs
+// pseudo-filesystem.
+class Task {
+ public:
+ explicit Task(pid_t task_id);
+
+ bool IsValid() const { return task_fd_.get() >= 0; }
+ explicit operator bool() const { return IsValid(); }
+
+ pid_t task_id() const { return task_id_; }
+ std::string name() const { return name_; }
+ pid_t thread_group_id() const { return thread_group_id_; }
+ pid_t parent_process_id() const { return parent_process_id_; }
+ size_t thread_count() const { return thread_count_; }
+ uint32_t cpus_allowed_mask() const { return cpus_allowed_mask_; }
+ const std::string& cpus_allowed_list() const { return cpus_allowed_list_; }
+ const std::array<int, 4>& user_id() const { return user_id_; }
+ const std::array<int, 4>& group_id() const { return group_id_; }
+
+ // Indices into user and group id arrays.
+ enum {
+ kUidReal = 0,
+ kUidEffective,
+ kUidSavedSet,
+ kUidFilesystem,
+ };
+
+ std::string GetCpuSetPath() const;
+
+ private:
+ pid_t task_id_;
+ base::unique_fd task_fd_;
+
+ // Fields read from /proc/<task_id_>/status.
+ std::string name_;
+ pid_t thread_group_id_;
+ pid_t parent_process_id_;
+ std::array<int, 4> user_id_;
+ std::array<int, 4> group_id_;
+ size_t thread_count_;
+ uint32_t cpus_allowed_mask_;
+ std::string cpus_allowed_list_;
+
+ // Opens the file /proc/<task_id_>/|name| and returns the open file
+ // descriptor.
+ base::unique_fd OpenTaskFile(const std::string& name) const;
+
+ // Similar to OpenTaskFile() but returns a file pointer.
+ UniqueFile OpenTaskFilePointer(const std::string& name) const;
+
+ // Reads the field named |field| from /proc/<task_id_>/status.
+ std::string GetStatusField(const std::string& field) const;
+
+ // Reads a subset of the fields in /proc/<task_id_>/status.
+ void ReadStatusFields();
+
+ Task(const Task&) = delete;
+ void operator=(const Task&) = delete;
+};
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_TASK_H_
diff --git a/services/vr/performanced/unique_file.h b/services/vr/performanced/unique_file.h
new file mode 100644
index 0000000..86e487a
--- /dev/null
+++ b/services/vr/performanced/unique_file.h
@@ -0,0 +1,20 @@
+#ifndef ANDROID_DVR_PERFORMANCED_UNIQUE_FILE_H_
+#define ANDROID_DVR_PERFORMANCED_UNIQUE_FILE_H_
+
+#include <stdio.h>
+
+#include <memory>
+
+namespace android {
+namespace dvr {
+
+// Utility to manage the lifetime of a file pointer.
+struct FileDeleter {
+ void operator()(FILE* fp) { fclose(fp); }
+};
+using UniqueFile = std::unique_ptr<FILE, FileDeleter>;
+
+} // namespace dvr
+} // namespace android
+
+#endif // ANDROID_DVR_PERFORMANCED_UNIQUE_FILE_H_