summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-09-08 16:57:37 -0700
committerXin Li <delphij@google.com>2020-09-08 16:57:37 -0700
commitd1299a41b4bdd9bdfd7d339fedde7fb433569f5e (patch)
tree72379b91ee508a91522937d7a2fa6640e1e4d2f6 /src
parent806a07b868ac808bbd7ef09c37d6bd180e15de36 (diff)
parentd3905c2fcaeda7eecdea063d5351d7009f62cbe0 (diff)
downloadplatform_system_iorap-master.tar.gz
platform_system_iorap-master.tar.bz2
platform_system_iorap-master.zip
Merge Android RHEADmaster
Bug: 168057903 Merged-In: I3c77335b908d1ae9b675ef482589858ed27a9b97 Change-Id: I36f1c71de91f477257efed4c541bea519457b918
Diffstat (limited to 'src')
-rw-r--r--src/binder/iiorap_def.h5
-rw-r--r--src/binder/iiorap_impl.cc223
-rw-r--r--src/binder/iiorap_impl.h6
-rw-r--r--src/binder/package_change_observer.cc37
-rw-r--r--src/binder/package_change_observer.h42
-rw-r--r--src/binder/package_manager_remote.cc194
-rw-r--r--src/binder/package_manager_remote.h83
-rw-r--r--src/binder/package_version_map.cc117
-rw-r--r--src/binder/package_version_map.h83
-rw-r--r--src/common/async_pool.h91
-rw-r--r--src/common/cmd_utils.h170
-rw-r--r--src/common/expected.h8
-rw-r--r--src/common/loggers.h56
-rw-r--r--src/common/printer.h62
-rw-r--r--src/common/rx_async.h77
-rw-r--r--src/common/trace.h48
-rw-r--r--src/compiler/compiler.cc949
-rw-r--r--src/compiler/compiler.h65
-rw-r--r--src/compiler/main.cc207
-rw-r--r--src/db/app_component_name.h130
-rw-r--r--src/db/clean_up.cc115
-rw-r--r--src/db/clean_up.h54
-rw-r--r--src/db/file_models.cc181
-rw-r--r--src/db/file_models.h124
-rw-r--r--src/db/main.cc230
-rw-r--r--src/db/models.cc21
-rw-r--r--src/db/models.h1130
-rw-r--r--src/inode2filename/data_source.cc187
-rw-r--r--src/inode2filename/data_source.h74
-rw-r--r--src/inode2filename/inode.cc6
-rw-r--r--src/inode2filename/inode.h89
-rw-r--r--src/inode2filename/inode_resolver.cc207
-rw-r--r--src/inode2filename/inode_resolver.h121
-rw-r--r--src/inode2filename/inode_result.cc62
-rw-r--r--src/inode2filename/inode_result.h82
-rw-r--r--src/inode2filename/main.cc356
-rw-r--r--src/inode2filename/out_of_process_inode_resolver.cc425
-rw-r--r--src/inode2filename/out_of_process_inode_resolver.h44
-rw-r--r--src/inode2filename/search_directories.cc497
-rw-r--r--src/inode2filename/search_directories.h75
-rw-r--r--src/iorapd/main.cc47
-rw-r--r--src/maintenance/controller.cc620
-rw-r--r--src/maintenance/controller.h113
-rw-r--r--src/maintenance/db_cleaner.cc66
-rw-r--r--src/maintenance/db_cleaner.h34
-rw-r--r--src/maintenance/main.cc194
-rw-r--r--src/manager/event_manager.cc1101
-rw-r--r--src/manager/event_manager.h66
-rw-r--r--src/perfetto/perfetto_consumer.cc608
-rw-r--r--src/perfetto/perfetto_consumer.h35
-rw-r--r--src/perfetto/rx_producer.cc100
-rw-r--r--src/perfetto/rx_producer.h53
-rw-r--r--src/prefetcher/main.cc190
-rw-r--r--src/prefetcher/main_client.cc160
-rw-r--r--src/prefetcher/minijail.cc49
-rw-r--r--src/prefetcher/minijail.h23
-rw-r--r--src/prefetcher/prefetcher_daemon.cc1367
-rw-r--r--src/prefetcher/prefetcher_daemon.h130
-rw-r--r--src/prefetcher/read_ahead.cc435
-rw-r--r--src/prefetcher/read_ahead.h63
-rw-r--r--src/prefetcher/session.cc724
-rw-r--r--src/prefetcher/session.h236
-rw-r--r--src/prefetcher/session_manager.cc281
-rw-r--r--src/prefetcher/session_manager.h91
-rw-r--r--src/prefetcher/task_id.h39
-rw-r--r--src/serialize/TraceFile.proto47
-rw-r--r--src/serialize/arena_ptr.h76
-rw-r--r--src/serialize/protobuf_io.cc173
-rw-r--r--src/serialize/protobuf_io.h64
69 files changed, 13671 insertions, 247 deletions
diff --git a/src/binder/iiorap_def.h b/src/binder/iiorap_def.h
index 0c1eac8..5763380 100644
--- a/src/binder/iiorap_def.h
+++ b/src/binder/iiorap_def.h
@@ -29,6 +29,9 @@ FN_BEGIN(::com::google::android::startup::iorap::,IIorap)
FN(setTaskListener, (const ::android::sp<::com::google::android::startop::iorap::,ITaskListener,>&,listener)) /*NOLINT*/ \
FN(onAppLaunchEvent,(const ::com::google::android::startop::iorap::,RequestId,&,request), \
(const ::com::google::android::startop::iorap::,AppLaunchEvent,&,event)) \
+FN(onJobScheduledEvent, \
+ (const ::com::google::android::startop::iorap::,RequestId,&,request), \
+ (const ::com::google::android::startop::iorap::,JobScheduledEvent,&,event)) \
FN(onPackageEvent, (const ::com::google::android::startop::iorap::,RequestId,&,request), \
(const ::com::google::android::startop::iorap::,PackageEvent,&,event)) \
FN(onAppIntentEvent,(const ::com::google::android::startop::iorap::,RequestId,&,request), \
@@ -39,6 +42,8 @@ FN(onSystemServiceEvent,
FN(onSystemServiceUserEvent, \
(const ::com::google::android::startop::iorap::,RequestId,&,request), \
(const ::com::google::android::startop::iorap::,SystemServiceUserEvent,&,event))\
+FN(onDexOptEvent, (const ::com::google::android::startop::iorap::,RequestId,&,request), \
+ (const ::com::google::android::startop::iorap::,DexOptEvent,&,event)) \
FN_END() \
// Convenience macros to unpack the 2nd parameter from IIORAP_IFACE_DEF#FN calls.
diff --git a/src/binder/iiorap_impl.cc b/src/binder/iiorap_impl.cc
index a6f6409..de5a55d 100644
--- a/src/binder/iiorap_impl.cc
+++ b/src/binder/iiorap_impl.cc
@@ -24,6 +24,11 @@
#include <binder/BinderService.h>
#include <binder/IPCThreadState.h>
#include <include/binder/request_id.h>
+#include <utils/Printer.h>
+
+#include <codecvt>
+#include <locale>
+#include <utility>
/*
* Definitions for the IIorap binder native service implementation.
@@ -76,10 +81,42 @@ static std::atomic<bool> s_service_params_ready_{false};
static ServiceParams s_service_params_;
static std::atomic<ServiceParams*> s_service_params_atomic_;
+// Convert an android::String16 (UTF-16) to a UTF-8 std::string.
+static std::string String16ToStdString(const ::android::String16& s16) {
+ std::u16string u16{s16.string()};
+ std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
+
+ std::string res = convert.to_bytes(u16);
+ return res;
+}
+
} // namespace anonymous
class IIorapImpl::Impl {
+ // ITaskListener implementation for iorap::manager::EventManager.
+ struct EventManagerTaskCallbacks : public iorap::manager::TaskResultCallbacks {
+ explicit EventManagerTaskCallbacks(iorap::borrowed<IIorapImpl::Impl*> impl) {
+ CHECK(impl != nullptr);
+ impl_ = impl;
+ }
+
+ virtual void OnProgress(iorap::binder::RequestId request_id, iorap::binder::TaskResult task_result) override {
+ impl_->ReplyWithResult(request_id, /*completed*/false, std::move(task_result));
+ }
+ virtual void OnComplete(iorap::binder::RequestId request_id, iorap::binder::TaskResult task_result) override {
+ impl_->ReplyWithResult(request_id, /*completed*/true, std::move(task_result));
+ }
+
+ virtual ~EventManagerTaskCallbacks() {}
+
+ iorap::borrowed<IIorapImpl::Impl*> impl_;
+ };
+
public:
+ ~Impl() {
+ package_manager_->UnregisterPackageChangeObserver(package_change_observer_);
+ }
+
void SetTaskListener(const ::android::sp<ITaskListener>& listener) {
::android::sp<ITaskListener> old_listener = listener_;
if (old_listener != nullptr && listener != nullptr) {
@@ -93,7 +130,7 @@ class IIorapImpl::Impl {
if (listener == nullptr) {
// No listener. Cannot send anything back to the client.
// This could be normal, e.g. client had set listener to null before disconnecting.
- LOG(WARNING) << "Drop result, no listener registered.";
+ LOG(DEBUG) << "Drop result, no listener registered.";
// TODO: print the result with ostream operator<<
return;
}
@@ -111,6 +148,26 @@ class IIorapImpl::Impl {
}
}
+ void ReplyWithResult(const RequestId& request_id, bool completed, TaskResult result) {
+ ::android::sp<ITaskListener> listener = listener_;
+ if (listener == nullptr) {
+ // No listener. Cannot send anything back to the client.
+ // This could be normal, e.g. client had set listener to null before disconnecting.
+ LOG(DEBUG) << "Drop result, no listener registered.";
+ // TODO: print the result with ostream operator<<
+ return;
+ }
+
+ // TODO: verbose, not info.
+ if (completed) {
+ LOG(VERBOSE) << "ITaskListener::onComplete (request_id=" << request_id.request_id << ")";
+ listener->onComplete(request_id, result);
+ } else {
+ LOG(VERBOSE) << "ITaskListener::onProgress (request_id=" << request_id.request_id << ")";
+ listener->onProgress(request_id, result);
+ }
+ }
+
bool OnAppLaunchEvent(const RequestId& request_id,
const AppLaunchEvent& event) {
if (MaybeHandleFakeBehavior(request_id)) {
@@ -120,6 +177,89 @@ class IIorapImpl::Impl {
return service_params_.event_manager_->OnAppLaunchEvent(request_id, event);
}
+ bool OnDexOptEvent(const RequestId& request_id, const DexOptEvent& event) {
+ if (MaybeHandleFakeBehavior(request_id)) {
+ return true;
+ }
+
+ return service_params_.event_manager_->OnDexOptEvent(request_id, event);
+ }
+
+ bool OnJobScheduledEvent(const RequestId& request_id,
+ const JobScheduledEvent& event) {
+ if (MaybeHandleFakeBehavior(request_id)) {
+ return true;
+ }
+
+ return service_params_.event_manager_->OnJobScheduledEvent(request_id, event);
+ }
+
+ void Dump(/*borrow*/::android::Printer& printer,
+ const ::android::Vector<::android::String16>& args) {
+
+ if (args.size() == 0) {
+ service_params_.event_manager_->Dump(/*borrow*/printer);
+ return;
+ }
+
+ ::android::String16 arg_prev;
+ for (const ::android::String16& arg : args) {
+ bool unknown = false;
+ if (arg == ::android::String16("--all") || arg == ::android::String16("-a")) {
+ // using 'dumpsys' or 'bugreport' passes a single '-a' flag to this function.
+ service_params_.event_manager_->Dump(/*borrow*/printer);
+ } else if (arg == ::android::String16("--refresh-properties")) {
+ service_params_.event_manager_->RefreshSystemProperties(/*borrow*/printer);
+ printer.printLine("System properties refreshed.");
+ } else if (arg == ::android::String16("--compile-package")) {
+ // Intentionally left blank.
+ } else if (arg_prev == ::android::String16("--compile-package")) {
+ std::string package_name = String16ToStdString(arg);
+
+ if (!service_params_.event_manager_->CompilePackage(/*borrow*/printer, package_name)) {
+ printer.printFormatLine("Failed to compile package %s.", package_name.c_str());
+ } else {
+ printer.printFormatLine("Package %s compiled.", package_name.c_str());
+ }
+ } else if (arg == ::android::String16("--purge-package")) {
+ // Intentionally left blank.
+ } else if (arg_prev == ::android::String16("--purge-package")) {
+ std::string package_name = String16ToStdString(arg);
+
+ if (!service_params_.event_manager_->PurgePackage(/*borrow*/printer, package_name)) {
+ printer.printFormatLine("Failed to purge package %s.", package_name.c_str());
+ } else {
+ printer.printFormatLine("Package %s purged.", package_name.c_str());
+ }
+ } else {
+ unknown = true;
+ }
+
+ if (unknown && arg != ::android::String16("--help")) {
+ printer.printLine("Invalid arguments.");
+ printer.printLine("");
+
+ printer.printLine("Arguments were:");
+ for (const ::android::String16& arg16 : args) {
+ printer.printFormatLine(" '%s'", String16ToStdString(arg16).c_str());
+ }
+ printer.printLine("");
+ }
+
+ if (unknown || arg == ::android::String16("--help")) {
+ printer.printLine("Iorapd dumpsys commands:");
+ printer.printLine(" (none),--all,-a: Print state information for debugging iorapd.");
+ printer.printLine(" --help: Display this help menu");
+ printer.printLine(" --compile-package <name>: Compile single package on device");
+ printer.printLine(" --purge-package <name>: Delete database entries/files for package");
+ printer.printLine(" --refresh-properties: Refresh system properties");
+ return;
+ }
+
+ arg_prev = arg;
+ }
+ }
+
void HandleFakeBehavior(const RequestId& request_id) {
DCHECK(service_params_.fake_);
@@ -141,11 +281,36 @@ class IIorapImpl::Impl {
::android::sp<ITaskListener> listener_;
- Impl(ServiceParams p) : service_params_{std::move(p)} {
+ Impl(ServiceParams p) : service_params_{std::move(p)}, event_manager_callbacks_{new EventManagerTaskCallbacks{this}} {
CHECK(service_params_.event_manager_ != nullptr);
+
+ service_params_.event_manager_->SetTaskResultCallbacks(
+ std::static_pointer_cast<manager::TaskResultCallbacks>(event_manager_callbacks_));
+
+ // Init the package change observer.
+ package_manager_ = PackageManagerRemote::Create();
+
+ if (package_manager_ == nullptr) {
+ LOG(FATAL) << "Failed to get package manager service in IIorapImpl::Impl";
+ return;
+ }
+
+ package_change_observer_ =
+ new PackageChangeObserver(service_params_.event_manager_);
+ package_manager_death_recipient_ =
+ new PackageManagerDeathRecipient(package_manager_, package_change_observer_);
+
+ package_manager_->RegisterPackageChangeObserver(package_change_observer_);
+ package_manager_->
+ RegisterPackageManagerDeathRecipient(package_manager_death_recipient_);
+
}
ServiceParams service_params_;
+ std::shared_ptr<EventManagerTaskCallbacks> event_manager_callbacks_;
+ android::sp<PackageChangeObserver> package_change_observer_;
+ android::sp<PackageManagerDeathRecipient> package_manager_death_recipient_;
+ std::shared_ptr<PackageManagerRemote> package_manager_;
};
using Impl = IIorapImpl::Impl;
@@ -203,9 +368,21 @@ bool IIorapImpl::Start(std::shared_ptr<manager::EventManager> event_manager) {
// Release edge synchronizes-with the top of this function.
s_service_started_.store(true);
+ // TODO: IIRC thread-start(t1) synchronizes-with t1.main, so we should be able
+ // to delete the majority of atomics for any pre-thread-start initialization...
+
return true;
}
+::android::status_t IIorapImpl::dump(int fd, const ::android::Vector<::android::String16>& args) {
+
+ ::android::FdPrinter printer{fd};
+
+ impl_->Dump(printer, args);
+
+ return ::android::NO_ERROR;
+}
+
namespace {
#define MAYBE_HAVE_FAKE_BEHAVIOR(self, request_id) \
@@ -260,6 +437,48 @@ Status SendArgs(const char* function_name,
}
template <typename ... Args>
+Status SendArgs(const char* function_name,
+ Impl* self,
+ const RequestId& request_id,
+ const DexOptEvent& event) {
+ DCHECK_EQ(std::string(function_name), "onDexOptEvent");
+ LOG(VERBOSE) << "IIorap::onDexOptEvent";
+
+ MAYBE_HAVE_FAKE_BEHAVIOR(self, request_id);
+
+ if (self->OnDexOptEvent(request_id, event)) {
+ return Status::ok();
+ } else {
+ return Status::fromStatusT(::android::BAD_VALUE);
+ }
+}
+
+template <typename ... Args>
+Status SendArgs(const char* function_name,
+ Impl* self,
+ const RequestId& request_id,
+ const JobScheduledEvent& event) {
+ DCHECK_EQ(std::string(function_name), "onJobScheduledEvent");
+ LOG(VERBOSE) << "IIorap::onJobScheduledEvent";
+
+ MAYBE_HAVE_FAKE_BEHAVIOR(self, request_id);
+
+ if (self->OnJobScheduledEvent(request_id, event)) {
+ return Status::ok();
+ } else {
+ // TODO: I suppose this should write out an exception back,
+ // like a service-specific error or something.
+ //
+ // It depends on whether or not we even have any synchronous
+ // errors.
+ //
+ // Most of the work here is done async, so it should handle
+ // async callbacks.
+ return Status::fromStatusT(::android::BAD_VALUE);
+ }
+}
+
+template <typename ... Args>
Status Send(const char* function_name, Args&&... args) {
LOG(VERBOSE) << "IIorap::Send(" << function_name << ")";
diff --git a/src/binder/iiorap_impl.h b/src/binder/iiorap_impl.h
index 811497f..ae1422f 100644
--- a/src/binder/iiorap_impl.h
+++ b/src/binder/iiorap_impl.h
@@ -18,6 +18,8 @@
#define IORAP_BINDER_IIORAP_IMPL_H
#include "binder/iiorap_def.h"
+#include "binder/package_change_observer.h"
+#include "binder/package_manager_remote.h"
#include "common/macros.h"
#include "com/google/android/startop/iorap/BnIorap.h"
@@ -45,6 +47,9 @@ public:
static bool Start(std::shared_ptr<iorap::manager::EventManager> event_manager);
static constexpr const char* getServiceName() { return "iorapd"; };
+ virtual ::android::status_t dump(int fd,
+ const ::android::Vector<::android::String16>& args) override;
+
// Join all parameter declarations by splitting each parameter with a comma.
// Types are used fully.
#define IIORAP_IMPL_ARGS(...) \
@@ -65,7 +70,6 @@ private:
std::unique_ptr<Impl> impl_;
};
-
}
}
diff --git a/src/binder/package_change_observer.cc b/src/binder/package_change_observer.cc
new file mode 100644
index 0000000..1907af8
--- /dev/null
+++ b/src/binder/package_change_observer.cc
@@ -0,0 +1,37 @@
+// 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 "package_change_observer.h"
+#include "package_manager_remote.h"
+#include "manager/event_manager.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+namespace iorap::binder {
+
+PackageChangeObserver::PackageChangeObserver(
+ std::shared_ptr<iorap::manager::EventManager> event_manager) :
+ event_manager_(event_manager){}
+
+android::binder::Status PackageChangeObserver::onPackageChanged(
+ const android::content::pm::PackageChangeEvent& event) {
+ LOG(DEBUG) << "Received PackageChangeObserver::onPackageChanged";
+ if (event_manager_->OnPackageChanged(event)) {
+ return android::binder::Status::ok();
+ } else {
+ return android::binder::Status::fromStatusT(android::BAD_VALUE);
+ }
+}
+} // namespace iorap::binder
diff --git a/src/binder/package_change_observer.h b/src/binder/package_change_observer.h
new file mode 100644
index 0000000..ac488ac
--- /dev/null
+++ b/src/binder/package_change_observer.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef IORAP_SRC_PACKAGE_CHANGE_OBSERVER_H_
+#define IORAP_SRC_PACKAGE_CHANGE_OBSERVER_H_
+
+#include "binder/iiorap_def.h"
+
+#include <android/content/pm/BnPackageChangeObserver.h>
+#include <android/content/pm/PackageChangeEvent.h>
+#include <android/content/pm/IPackageManagerNative.h>
+
+namespace iorap::manager {
+ class EventManager;
+};
+
+namespace iorap::binder {
+
+class PackageChangeObserver : public android::content::pm::BnPackageChangeObserver {
+ public:
+ PackageChangeObserver(std::shared_ptr<iorap::manager::EventManager> event_manager);
+
+ // Callback when the package is changed.
+ android::binder::Status onPackageChanged(
+ const ::android::content::pm::PackageChangeEvent& event) override;
+ private:
+ std::shared_ptr<iorap::manager::EventManager> event_manager_;
+};
+} // namespace iorap::binder
+
+#endif // IORAP_SRC_PACKAGE_CHANGE_OBSERVER_H_
diff --git a/src/binder/package_manager_remote.cc b/src/binder/package_manager_remote.cc
new file mode 100644
index 0000000..daeb6af
--- /dev/null
+++ b/src/binder/package_manager_remote.cc
@@ -0,0 +1,194 @@
+// 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 "package_manager_remote.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+#include <chrono>
+#include <thread>
+
+namespace iorap::binder {
+
+const constexpr int64_t kTimeoutMs = 60 * 1000; // 1 min
+const constexpr int64_t kIntervalMs = 1000; // 1 sec
+
+std::shared_ptr<PackageManagerRemote> PackageManagerRemote::Create() {
+ std::shared_ptr<PackageManagerRemote> package_manager =
+ std::make_shared<PackageManagerRemote>();
+ if (package_manager->ReconnectWithTimeout(kTimeoutMs)) {
+ return package_manager;
+ }
+ return nullptr;
+}
+
+android::sp<IPackageManager> PackageManagerRemote::GetPackageService() {
+ android::sp<android::IBinder> binder = android::defaultServiceManager()->getService(
+ android::String16{"package_native"});
+ if (binder == nullptr) {
+ LOG(ERROR) << "Cannot get package manager service!";
+ return nullptr;
+ }
+
+ return android::interface_cast<IPackageManager>(binder);
+}
+
+std::optional<int64_t> PackageManagerRemote::GetPackageVersion(
+ const std::string& package_name) {
+ int64_t version_code;
+ android::binder::Status status = InvokeRemote(
+ [this, &version_code, &package_name]() {
+ return package_service_->getVersionCodeForPackage(
+ android::String16(package_name.c_str()), /*out*/&version_code);
+ });
+ if (status.isOk()) {
+ return version_code;
+ } else {
+ LOG(WARNING) << "Failed to get version: "
+ << status.toString8().c_str()
+ << " for " << package_name
+ << ". Retry to connect package manager service.";
+ return std::nullopt;
+ }
+}
+
+std::optional<VersionMap> PackageManagerRemote::GetPackageVersionMap() {
+ VersionMap package_version_map;
+ std::optional<std::vector<std::string>> packages = GetAllPackages();
+ if (!packages) {
+ LOG(DEBUG) << "Failed to get all packages. The package manager may be down.";
+ return std::nullopt;
+ }
+ LOG(DEBUG) << "PackageManagerRemote::GetPackageVersionMap: "
+ << packages->size()
+ << " packages are found.";
+
+ for (const std::string& package : *packages) {
+ std::optional<int64_t> version = GetPackageVersion(package);
+ if (!version) {
+ LOG(DEBUG) << "Cannot get version for " << package
+ << "Package manager may be down";
+ return std::nullopt;
+ }
+ package_version_map[package] = *version;
+ }
+
+ return package_version_map;
+}
+
+std::optional<std::vector<std::string>> PackageManagerRemote::GetAllPackages() {
+ std::vector<std::string> packages;
+ android::binder::Status status = InvokeRemote(
+ [this, &packages]() {
+ return package_service_->getAllPackages(/*out*/&packages);
+ });
+
+ if (status.isOk()) {
+ return packages;
+ }
+
+ LOG(ERROR) << "Failed to get all packages: " << status.toString8().c_str();
+ return std::nullopt;
+
+}
+
+bool PackageManagerRemote::ReconnectWithTimeout(int64_t timeout_ms) {
+ int64_t count = 0;
+ package_service_ = nullptr;
+ std::chrono::duration interval = std::chrono::milliseconds(1000);
+ std::chrono::duration timeout = std::chrono::milliseconds(timeout_ms);
+
+ while (package_service_ == nullptr) {
+ LOG(WARNING) << "Reconnect to package manager service: " << ++count << " times";
+ package_service_ = GetPackageService();
+ std::this_thread::sleep_for(interval);
+ if (count * interval >= timeout) {
+ LOG(FATAL) << "Fail to create version map in "
+ << timeout.count()
+ << " milliseconds."
+ << " Reason: Failed to connect to package manager service."
+ << " Is system_server down?";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename T>
+android::binder::Status PackageManagerRemote::InvokeRemote(T&& lambda) {
+ android::binder::Status status =
+ static_cast<android::binder::Status>(lambda());
+ if (status.isOk()) {
+ return status;
+ }
+
+ if (!ReconnectWithTimeout(kTimeoutMs)) {
+ return status;
+ }
+
+ return lambda();
+}
+
+void PackageManagerRemote::RegisterPackageChangeObserver(
+ android::sp<PackageChangeObserver> observer) {
+ LOG(DEBUG) << "Register package change observer.";
+ android::binder::Status status = InvokeRemote(
+ [this, &observer]() {
+ return package_service_->registerPackageChangeObserver(observer);
+ });
+
+ if (!status.isOk()) {
+ LOG(FATAL) << "Cannot register package change observer.";
+ }
+}
+
+void PackageManagerRemote::UnregisterPackageChangeObserver(
+ android::sp<PackageChangeObserver> observer) {
+ LOG(DEBUG) << "Unregister package change observer.";
+ android::binder::Status status = InvokeRemote(
+ [this, &observer]() {
+ return package_service_->unregisterPackageChangeObserver(observer);
+ });
+
+ if (!status.isOk()) {
+ LOG(WARNING) << "Cannot unregister package change observer.";
+ }
+}
+
+void PackageManagerRemote::RegisterPackageManagerDeathRecipient(
+ android::sp<PackageManagerDeathRecipient> death_recipient) {
+ LOG(DEBUG) << "Register package manager death recipient.";
+ android::status_t status =
+ android::IInterface::asBinder(package_service_.get())->linkToDeath(death_recipient);
+
+ if (status == android::OK) {
+ return;
+ }
+
+ if (!ReconnectWithTimeout(kTimeoutMs) ||
+ android::OK != android::IInterface::asBinder(
+ package_service_.get())->linkToDeath(death_recipient)) {
+ LOG(FATAL) << "Failed to register package manager death recipient.";
+ }
+}
+
+void PackageManagerDeathRecipient::binderDied(const android::wp<android::IBinder>& /* who */) {
+ LOG(DEBUG) << "PackageManagerDeathRecipient::binderDied try to re-register";
+ package_manager_->RegisterPackageChangeObserver(observer_);
+ package_manager_->
+ RegisterPackageManagerDeathRecipient(this);
+}
+} // namespace iorap::package_manager
diff --git a/src/binder/package_manager_remote.h b/src/binder/package_manager_remote.h
new file mode 100644
index 0000000..9b0dcd9
--- /dev/null
+++ b/src/binder/package_manager_remote.h
@@ -0,0 +1,83 @@
+// 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.
+
+#ifndef IORAP_SRC_PACKAGE_MANAGER_REMOTE_H_
+#define IORAP_SRC_PACKAGE_MANAGER_REMOTE_H_
+
+#include "binder/package_change_observer.h"
+
+#include <android/content/pm/IPackageManagerNative.h>
+#include <binder/IServiceManager.h>
+
+#include <optional>
+#include <unordered_map>
+
+namespace iorap::binder {
+
+using IPackageManager = android::content::pm::IPackageManagerNative;
+// A map between package name and its version.
+using VersionMap = std::unordered_map<std::string, int64_t>;
+
+class PackageManagerRemote;
+
+class PackageManagerDeathRecipient : public android::IBinder::DeathRecipient {
+public:
+ PackageManagerDeathRecipient(std::shared_ptr<PackageManagerRemote> package_manager,
+ android::sp<PackageChangeObserver> observer) :
+ package_manager_(package_manager), observer_(observer) {}
+ // android::IBinder::DeathRecipient override:
+ void binderDied(const android::wp<android::IBinder>& /* who */) override;
+
+private:
+ std::shared_ptr<PackageManagerRemote> package_manager_;
+
+ android::sp<PackageChangeObserver> observer_;
+};
+
+class PackageManagerRemote {
+ public:
+ static std::shared_ptr<PackageManagerRemote> Create();
+
+ // Gets the package version based on the package name.
+ std::optional<int64_t> GetPackageVersion(const std::string& package_name);
+
+ // Gets a map of package name and its version.
+ std::optional<VersionMap> GetPackageVersionMap();
+
+ void RegisterPackageChangeObserver(android::sp<PackageChangeObserver> observer);
+
+ void UnregisterPackageChangeObserver(android::sp<PackageChangeObserver> observer);
+
+ void RegisterPackageManagerDeathRecipient(
+ android::sp<PackageManagerDeathRecipient> death_recipient);
+
+ private:
+ template <typename T>
+ android::binder::Status InvokeRemote(T&& lambda);
+
+ // Reconnects to the package manager service.
+ // Retry until timeout.
+ bool ReconnectWithTimeout(int64_t timeout_ms);
+
+ // Gets the package manager service.
+ static android::sp<IPackageManager> GetPackageService();
+
+ // Gets all package names.
+ std::optional<std::vector<std::string>> GetAllPackages();
+
+ android::sp<IPackageManager> package_service_;
+};
+} // namespace iorap::package_manager
+
+#endif // IORAP_SRC_PACKAGE_MANAGER_REMOTE_H_
diff --git a/src/binder/package_version_map.cc b/src/binder/package_version_map.cc
new file mode 100644
index 0000000..784bb1b
--- /dev/null
+++ b/src/binder/package_version_map.cc
@@ -0,0 +1,117 @@
+// 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 "package_version_map.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+namespace iorap::binder {
+
+std::shared_ptr<PackageVersionMap> PackageVersionMap::Create() {
+ std::shared_ptr<PackageManagerRemote> package_manager =
+ PackageManagerRemote::Create();
+ if (!package_manager) {
+ return std::make_shared<PackageVersionMap>();
+ }
+
+ std::optional<VersionMap> map = package_manager->GetPackageVersionMap();
+ return std::make_shared<PackageVersionMap>(package_manager, map);
+}
+
+void PackageVersionMap::UpdateAll() {std::lock_guard<std::mutex> lock(mutex_);
+ size_t old_size = version_map_->size();
+ std::optional<VersionMap> new_version_map =
+ package_manager_->GetPackageVersionMap();
+ if (!new_version_map) {
+ LOG(DEBUG) << "Failed to get the latest version map";
+ return;
+ }
+ version_map_ = std::move(new_version_map);
+ LOG(DEBUG) << "Update for version is done. The size is from " << old_size
+ << " to " << version_map_->size();
+}
+
+bool PackageVersionMap::Update(std::string package_name, int64_t version) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (!version_map_) {
+ LOG(DEBUG) << "The version map doesn't exist. "
+ << "The package manager may be down.";
+ return false;
+ }
+
+ VersionMap::iterator it = version_map_->find(package_name);
+ if (it == version_map_->end()) {
+ LOG(DEBUG) << "New installed package "
+ << package_name
+ << " with version "
+ << version;
+ (*version_map_)[package_name] = version;
+ return true;
+ }
+
+ if (it->second != version) {
+ LOG(DEBUG) << "New version package "
+ << package_name
+ << " with version "
+ << version;
+ (*version_map_)[package_name] = version;
+ return true;
+ }
+
+ LOG(DEBUG) << "Same version package "
+ << package_name
+ << " with version "
+ << version;
+ return false;
+}
+
+size_t PackageVersionMap::Size() {
+ if (!version_map_) {
+ LOG(DEBUG) << "The version map doesn't exist. "
+ << "The package manager may be down.";
+ return -1;
+ }
+ return version_map_->size();
+}
+
+std::optional<int64_t> PackageVersionMap::GetOrQueryPackageVersion(
+ const std::string& package_name) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (!version_map_) {
+ LOG(DEBUG) << "The version map doesn't exist. "
+ << "The package manager may be down.";
+ return std::nullopt;
+ }
+
+ VersionMap::iterator it = version_map_->find(package_name);
+ if (it == version_map_->end()) {
+ LOG(DEBUG) << "Cannot find version for: " << package_name
+ << " in the hash table";
+ std::optional<int64_t> version =
+ package_manager_->GetPackageVersion(package_name);
+ if (version) {
+ LOG(VERBOSE) << "Find version for: " << package_name << " on the fly.";
+ (*version_map_)[package_name] = *version;
+ return *version;
+ } else {
+ LOG(ERROR) << "Cannot find version for: " << package_name
+ << " on the fly.";
+ return -1;
+ }
+ }
+
+ return it->second;
+}
+} // namespace iorap::binder
diff --git a/src/binder/package_version_map.h b/src/binder/package_version_map.h
new file mode 100644
index 0000000..f2048fe
--- /dev/null
+++ b/src/binder/package_version_map.h
@@ -0,0 +1,83 @@
+// 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.
+
+#ifndef IORAP_SRC_PACKAGE_VERSION_MAP_H_
+#define IORAP_SRC_PACKAGE_VERSION_MAP_H_
+
+#include <android/content/pm/IPackageManagerNative.h>
+#include <binder/IServiceManager.h>
+
+#include <optional>
+#include <unordered_map>
+
+#include "package_manager_remote.h"
+
+namespace iorap::binder {
+
+class PackageVersionMap {
+ public:
+ static std::shared_ptr<PackageVersionMap> Create();
+
+ PackageVersionMap(std::shared_ptr<PackageManagerRemote> package_manager,
+ std::optional<VersionMap> version_map)
+ : package_manager_(package_manager),
+ version_map_(version_map) {}
+
+ PackageVersionMap()
+ : package_manager_(nullptr), version_map_(std::nullopt) {}
+
+ // Updates the version specified by 'package_name' to 'version'.
+ //
+ // Post-condition: Find(package_name) == version.
+ // * if the package is newly installed, insert and return true.
+ // * if the package version is changed, update the version to the
+ // given one and return true.
+ // * otherwise, return false.
+ bool Update(std::string package_name, int64_t version);
+
+ void UpdateAll();
+
+ // Finds the version of the package in the hash table.
+ // -1 means the app is installed by unversioned.
+ // Empty means the app is not inside the RAM version map, maybe due to
+ // the app is newly installed.
+ std::optional<int64_t> Find(const std::string& package_name) {
+ VersionMap::iterator it = version_map_->find(package_name);
+ if (it == version_map_->end()) {
+ return std::nullopt;
+ }
+ return it->second;
+ }
+
+ size_t Size();
+
+ // Gets or queries the version for the package.
+ //
+ // The method firstly access the hash map in the RAM, which is built when
+ // iorapd starts. If the version is not in the map, it tries the query
+ // the package manager via IPC, with a cost of ~0.6ms.
+ //
+ // If no version can be found for some reason, return -1,
+ // because when an app has no version the package manager returns -1.
+ std::optional<int64_t> GetOrQueryPackageVersion(
+ const std::string& package_name);
+
+ private:
+ std::shared_ptr<PackageManagerRemote> package_manager_;
+ std::optional<VersionMap> version_map_;
+ std::mutex mutex_;
+};
+} // namespace iorap::binder
+
+#endif // IORAP_SRC_PACKAGE_MANAGER_REMOTE_H_
diff --git a/src/common/async_pool.h b/src/common/async_pool.h
new file mode 100644
index 0000000..51cd38c
--- /dev/null
+++ b/src/common/async_pool.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2019 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 IORAP_SRC_COMMON_ASYNC_POOL_H_
+#define IORAP_SRC_COMMON_ASYNC_POOL_H_
+
+#include <atomic>
+#include <vector>
+#include <deque>
+#include <future>
+
+#include <condition_variable>
+#include <future>
+
+namespace iorap::common {
+
+class AsyncPool {
+ std::atomic<bool> shutting_down_{false};
+ std::deque<std::future<void>> futures_;
+
+ std::mutex mutex_;
+ std::condition_variable cond_var_;
+
+ public:
+
+ // Any threads calling 'Join' should eventually unblock
+ // once all functors have run to completition.
+ void Shutdown() {
+ shutting_down_ = true;
+
+ cond_var_.notify_all();
+ }
+
+ // Block forever until Shutdown is called *and* all
+ // functors passed to 'LaunchAsync' have run to completition.
+ void Join() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ while (true) {
+ // Pop all items eagerly
+ while (true) {
+ auto it = futures_.begin();
+ if (it == futures_.end()) {
+ break;
+ }
+
+ std::future<void> future = std::move(*it);
+ futures_.pop_front();
+
+ lock.unlock(); // do not stall callers of LaunchAsync
+ future.get();
+ lock.lock();
+ }
+
+ if (shutting_down_) {
+ break;
+ }
+
+ // Wait until we either get more items or ask to be shutdown.
+ cond_var_.wait(lock);
+ }
+ }
+
+ // Execute the functor 'u' in a new thread asynchronously.
+ // Using this spawns a new thread each time to immediately begin
+ // async execution.
+ template <typename T>
+ void LaunchAsync(T&& u) {
+ auto future = std::async(std::launch::async, std::forward<T>(u));
+
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ futures_.push_back(std::move(future));
+ }
+ cond_var_.notify_one();
+ }
+};
+
+} // namespace iorap::common
+
+#endif // IORAP_SRC_COMMON_ASYNC_POOL_H_
diff --git a/src/common/cmd_utils.h b/src/common/cmd_utils.h
new file mode 100644
index 0000000..4494119
--- /dev/null
+++ b/src/common/cmd_utils.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2019 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 IORAP_SRC_COMMON_CMD_UTILS_H_
+#define IORAP_SRC_COMMON_CMD_UTILS_H_
+
+#include <android-base/parsebool.h>
+#include <android-base/properties.h>
+
+#include <iostream>
+#include <sstream>
+#include <optional>
+#include <vector>
+
+namespace iorap::common {
+// Create execve-compatible argv.
+// The lifetime is tied to that of vector.
+inline std::unique_ptr<const char*[]> VecToArgv(const char* program_name,
+ const std::vector<std::string>& vector) {
+ // include program name in argv[0]
+ // include a NULL sentinel in the end.
+ std::unique_ptr<const char*[]> ptr{new const char*[vector.size() + 2]};
+
+ // program name
+ ptr[0] = program_name;
+
+ // all the argv
+ for (size_t i = 0; i < vector.size(); ++i) {
+ ptr[i+1] = vector[i].c_str();
+ }
+
+ // null sentinel
+ ptr[vector.size() + 1] = nullptr;
+
+ return ptr;
+}
+
+// Appends an args to the argv.
+template <class T>
+void AppendArgs(std::vector<std::string>& argv,
+ const T& value) {
+ std::stringstream ss;
+ ss << value;
+ argv.push_back(ss.str());
+}
+
+// Appends an args to the argv.
+template <class T, class T2>
+void AppendArgs(std::vector<std::string>& argv,
+ const T& value,
+ const T2& value2) {
+ AppendArgs(argv, value);
+ AppendArgs(argv, value2);
+}
+
+// Appends a named argument to the argv.
+//
+// For example if <name> is "--property" and <value> is int(200):
+// the string "--property=200" is appended to the argv.
+template <class T, class T2>
+void AppendNamedArg(std::vector<std::string>& argv,
+ const T& name,
+ const T2& value) {
+ std::stringstream ss;
+ ss << name;
+ ss << "=";
+ ss << value;
+
+ argv.push_back(ss.str());
+}
+
+// Appends args from a vector to the argv repeatedly to argv.
+//
+// For example, if <args> is "--timestamp" and <values> is [100, 200].
+// The "--timestamp 100" and "--timestamp 200" are appended.
+template <class T>
+void AppendArgsRepeatedly(std::vector<std::string>& argv,
+ std::string args,
+ const std::vector<T>& values) {
+ for (const T& v : values) {
+ AppendArgs(argv, args, v);
+ }
+}
+
+// Appends args from a vector to the argv repeatedly to argv.
+//
+// For example, if values is [input1.pb, input2.pb],
+// then the "input1.pb" and "input2.pb" are appended.
+template <class T>
+void AppendArgsRepeatedly(std::vector<std::string>& argv,
+ const std::vector<T>& values) {
+ for (const T& v : values) {
+ AppendArgs(argv, v);
+ }
+}
+
+// Appends a named argument to the argv repeatedly with different values.
+//
+// For example if <name> is "--property" and <value> is [int(200), int(400)]:
+// the strings "--property=200" and "--property=400" are both appended to the argv.
+template <class T, class T2>
+void AppendNamedArgRepeatedly(std::vector<std::string>& argv,
+ const T& name,
+ const std::vector<T2>& values) {
+ for (const T2& v :values) {
+ AppendNamedArg(argv, name, v);
+ }
+}
+
+// Get the value of the property.
+// Firstly, try to find the environment variable. If it does not exist,
+// try to get the property. If neither, use the default value..
+//
+// For example, for prop foo.bar.baz, it will first check for
+// FOO_BAR_BAZ environment variable.
+inline std::string GetEnvOrProperty(const std::string& prop, const std::string& default_val) {
+ std::string env_str = prop;
+ // a.b.c -> a_b_c
+ std::replace(env_str.begin(), env_str.end(), '.', '_');
+ // a_b_c -> A_B_C
+ std::transform(env_str.begin(), env_str.end(), env_str.begin(), ::toupper);
+ char *env = getenv(env_str.c_str());
+ if (env) {
+ return std::string(env);
+ }
+ return ::android::base::GetProperty(prop, default_val);
+}
+
+// Get the boolean value of the property.
+// Firstly, try to find the environment variable. If it does not exist,
+// try to get the property. If neither, use the default value..
+//
+// For example, for prop foo.bar.baz, it will first check for
+// FOO_BAR_BAZ environment variable.
+inline bool GetBoolEnvOrProperty(const std::string& prop, bool default_val) {
+ std::string env_str = prop;
+ // a.b.c -> a_b_c
+ std::replace(env_str.begin(), env_str.end(), '.', '_');
+ // a_b_c -> A_B_C
+ std::transform(env_str.begin(), env_str.end(), env_str.begin(), ::toupper);
+ char *env = getenv(env_str.c_str());
+ if (env) {
+ using ::android::base::ParseBoolResult;
+
+ switch (::android::base::ParseBool(env)) {
+ case ParseBoolResult::kError:
+ break;
+ case ParseBoolResult::kFalse:
+ return false;
+ case ParseBoolResult::kTrue:
+ return true;
+ }
+ }
+ return ::android::base::GetBoolProperty(prop, default_val);
+}
+
+} // namespace iorap::common
+
+#endif // IORAP_SRC_COMMON_CMD_UTILS_H_
diff --git a/src/common/expected.h b/src/common/expected.h
index 3d8eef7..c58e63f 100644
--- a/src/common/expected.h
+++ b/src/common/expected.h
@@ -292,7 +292,13 @@ struct expected {
return data_.value();
}
- // TODO: arrow operator?
+ constexpr const T* _Nonnull operator->() const {
+ return &data_.value();
+ }
+
+ constexpr T* _Nonnull operator->() {
+ return &data_.value();
+ }
constexpr T& value() & {
CHECK(has_value());
diff --git a/src/common/loggers.h b/src/common/loggers.h
new file mode 100644
index 0000000..1dbb2dc
--- /dev/null
+++ b/src/common/loggers.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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 IORAP_SRC_COMMON_LOGGERS
+#define IORAP_SRC_COMMON_LOGGERS
+
+#include <android-base/logging.h>
+
+namespace iorap {
+namespace common {
+
+// Log to both Stderr and Logd for convenience when running this from the command line.
+class StderrAndLogdLogger {
+ public:
+ explicit StderrAndLogdLogger(android::base::LogId default_log_id = android::base::MAIN)
+#ifdef __ANDROID__
+ : logd_(default_log_id)
+#endif
+ {
+ }
+
+ void operator()(::android::base::LogId id,
+ ::android::base::LogSeverity sev,
+ const char* tag,
+ const char* file,
+ unsigned int line,
+ const char* message) {
+#ifdef __ANDROID__
+ logd_(id, sev, tag, file, line, message);
+#endif
+ StderrLogger(id, sev, tag, file, line, message);
+ }
+
+ private:
+#ifdef __ANDROID__
+ ::android::base::LogdLogger logd_;
+#endif
+};
+
+} // namespace iorap
+} // namespace common
+
+#endif
diff --git a/src/common/printer.h b/src/common/printer.h
new file mode 100644
index 0000000..272e459
--- /dev/null
+++ b/src/common/printer.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef IORAP_UTILS_PRINTER_H_
+#define IORAP_UTILS_PRINTER_H_
+
+#include <utils/Printer.h>
+
+#include <string.h>
+
+namespace iorap::common {
+
+class StderrLogPrinter : public ::android::Printer {
+public:
+ // Create a printer using the specified logcat and log priority
+ // - Unless ignoreBlankLines is false, print blank lines to logcat
+ // (Note that the default ALOG behavior is to ignore blank lines)
+ StderrLogPrinter(const char* logtag,
+ android_LogPriority priority = ANDROID_LOG_DEBUG,
+ const char* prefix = nullptr,
+ bool ignore_blank_lines = false)
+ : log_printer_{logtag, priority, prefix, ignore_blank_lines} {
+ logtag_ = logtag;
+ prefix_ = prefix;
+ ignore_blank_lines_ = ignore_blank_lines;
+ }
+
+ // Print the specified line to logcat. No \n at the end is necessary.
+ virtual void printLine(const char* string) override {
+ if (ignore_blank_lines_ && strlen(string) == 0) {
+ return;
+ }
+ std::cerr << logtag_ << ": ";
+ if (prefix_ != nullptr) {
+ std::cerr << prefix_;
+ }
+ std::cerr << string << std::endl;
+ log_printer_.printLine(string);
+ }
+ private:
+ ::android::LogPrinter log_printer_;
+ const char* logtag_;
+ const char* prefix_;
+ bool ignore_blank_lines_;
+};
+
+} // namespace iorap::common
+
+#endif // IORAP_UTILS_PRINTER_H_
diff --git a/src/common/rx_async.h b/src/common/rx_async.h
new file mode 100644
index 0000000..d16f5ae
--- /dev/null
+++ b/src/common/rx_async.h
@@ -0,0 +1,77 @@
+// Copyright (C) 2019 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 IORAP_SRC_COMMON_RX_ASYNC_H_
+#define IORAP_SRC_COMMON_RX_ASYNC_H_
+
+#include "common/async_pool.h"
+
+#include <rxcpp/rx.hpp>
+
+namespace iorap::common {
+
+// Helper functions to operate with rx chains asynchronously.
+class RxAsync {
+ public:
+ // Subscribe to the observable on a new thread asynchronously.
+ // If no observe_on/subscribe_on is used, the chain will execute
+ // on that new thread.
+ //
+ // Returns the composite_subscription which can be used to
+ // unsubscribe from if we want to abort the chain early.
+ template <typename T, typename U>
+ static rxcpp::composite_subscription SubscribeAsync(
+ AsyncPool& async_pool,
+ T&& observable,
+ U&& subscriber) {
+ rxcpp::composite_subscription subscription;
+
+ async_pool.LaunchAsync([subscription, // safe copy: ref-counted
+ observable=std::forward<T>(observable),
+ subscriber=std::forward<U>(subscriber)]() mutable {
+ observable
+ .as_blocking()
+ .subscribe(subscription,
+ std::forward<decltype(subscriber)>(subscriber));
+ });
+
+ return subscription;
+ }
+
+ template <typename T, typename U, typename E>
+ static rxcpp::composite_subscription SubscribeAsync(
+ AsyncPool& async_pool,
+ T&& observable,
+ U&& on_next,
+ E&& on_error) {
+ rxcpp::composite_subscription subscription;
+
+ async_pool.LaunchAsync([subscription, // safe copy: ref-counted
+ observable=std::forward<T>(observable),
+ on_next=std::forward<U>(on_next),
+ on_error=std::forward<E>(on_error)]() mutable {
+ observable
+ .as_blocking()
+ .subscribe(subscription,
+ std::forward<decltype(on_next)>(on_next),
+ std::forward<decltype(on_error)>(on_error));
+ });
+
+ return subscription;
+ }
+};
+
+} // namespace iorap::common
+
+#endif // IORAP_SRC_COMMON_RX_ASYNC_H_
diff --git a/src/common/trace.h b/src/common/trace.h
new file mode 100644
index 0000000..a07d7bc
--- /dev/null
+++ b/src/common/trace.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef IORAP_COMMON_TRACE_H_
+#define IORAP_COMMON_TRACE_H_
+
+#include <cutils/trace.h>
+
+#include <cstdio>
+#include <sstream>
+
+namespace iorap {
+
+// TODO: refactor into utils/Trace.h
+
+class ScopedFormatTrace {
+ public:
+ template <typename ... Args>
+ ScopedFormatTrace(uint64_t tag, const char* fmt, Args&&... args) : tag_{tag} {
+ char buffer[1024];
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-security"
+ snprintf(buffer, sizeof(buffer), fmt, args...);
+#pragma GCC diagnostic pop
+ atrace_begin(tag, buffer);
+ }
+
+ ~ScopedFormatTrace() {
+ atrace_end(tag_);
+ }
+ private:
+ uint64_t tag_;
+};
+
+} // namespace iorap
+
+#endif // IORAP_COMMON_TRACE_H_
diff --git a/src/compiler/compiler.cc b/src/compiler/compiler.cc
new file mode 100644
index 0000000..7d0e624
--- /dev/null
+++ b/src/compiler/compiler.cc
@@ -0,0 +1,949 @@
+// Copyright (C) 2019 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 "compiler/compiler.h"
+
+#include "common/debug.h"
+#include "common/expected.h"
+
+#include "perfetto/rx_producer.h" // TODO: refactor BinaryWireProtobuf to separate header.
+#include "inode2filename/inode.h"
+#include "inode2filename/search_directories.h"
+#include "serialize/protobuf_io.h"
+
+#include <android-base/unique_fd.h>
+#include <android-base/parseint.h>
+#include <android-base/file.h>
+
+#include <perfetto/trace/trace.pb.h> // ::perfetto::protos::Trace
+#include <perfetto/trace/trace_packet.pb.h> // ::perfetto::protos::TracePacket
+
+#include "rxcpp/rx.hpp"
+#include <iostream>
+#include <fstream>
+#include <optional>
+#include <utility>
+#include <regex>
+
+#include <sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <syscall.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+namespace iorap::compiler {
+
+using Inode = iorap::inode2filename::Inode;
+using InodeResult = iorap::inode2filename::InodeResult;
+using SearchDirectories = iorap::inode2filename::SearchDirectories;
+
+template <typename T>
+using ProtobufPtr = iorap::perfetto::ProtobufPtr<T>;
+
+struct PerfettoTraceProtoInfo {
+ /* The perfetto trace proto. */
+ ::iorap::perfetto::PerfettoTraceProto proto;
+ /*
+ * The timestamp limit of the trace.
+ * It's used to truncate the trace file.
+ */
+ uint64_t timestamp_limit_ns;
+};
+
+struct PerfettoTracePtrInfo {
+ /* Deserialized protobuf data containing the perfetto trace. */
+ ProtobufPtr<::perfetto::protos::Trace> trace_ptr;
+ /*
+ * The timestamp limit of the trace.
+ * It's used to truncate the trace file.
+ */
+ uint64_t timestamp_limit_ns;
+};
+
+// Attempt to read protobufs from the filenames.
+// Emits one (or none) protobuf for each filename, in the same order as the filenames.
+// On any errors, the items are dropped (errors are written to the error LOG).
+//
+// All work is done on the same Coordinator as the Subscriber.
+template <typename ProtoT /*extends MessageLite*/>
+auto/*observable<PerfettoTracePtrInfo>*/ ReadProtosFromFileNames(
+ rxcpp::observable<CompilationInput> file_infos) {
+ using BinaryWireProtoT = ::iorap::perfetto::PerfettoTraceProto;
+
+ return file_infos
+ .map([](const CompilationInput& file_info) ->
+ std::optional<PerfettoTraceProtoInfo> {
+ LOG(VERBOSE) << "compiler::ReadProtosFromFileNames " << file_info.filename
+ << " TimeStampLimit "<< file_info.timestamp_limit_ns << " [begin]";
+ std::optional<BinaryWireProtoT> maybe_proto =
+ BinaryWireProtoT::ReadFullyFromFile(file_info.filename);
+ if (!maybe_proto) {
+ LOG(ERROR) << "Failed to read file: " << file_info.filename;
+ return std::nullopt;
+ }
+ return {{std::move(maybe_proto.value()), file_info.timestamp_limit_ns}};
+ })
+ .filter([](const std::optional<PerfettoTraceProtoInfo>& proto_info) {
+ return proto_info.has_value();
+ })
+ .map([](std::optional<PerfettoTraceProtoInfo>& proto_info) ->
+ PerfettoTraceProtoInfo {
+ return proto_info.value();
+ }) // TODO: refactor to something that flattens the optional, and logs in one operator.
+ .map([](PerfettoTraceProtoInfo& proto_info) ->
+ std::optional<PerfettoTracePtrInfo> {
+ std::optional<ProtobufPtr<ProtoT>> t = proto_info.proto.template MaybeUnserialize<ProtoT>();
+ if (!t) {
+ LOG(ERROR) << "Failed to parse protobuf: "; // TODO: filename.
+ return std::nullopt;
+ }
+ return {{std::move(t.value()), proto_info.timestamp_limit_ns}};
+ })
+ .filter([](const std::optional<PerfettoTracePtrInfo>& trace_info) {
+ return trace_info.has_value();
+ })
+ .map([](std::optional<PerfettoTracePtrInfo>& trace_info) ->
+ PerfettoTracePtrInfo {
+ LOG(VERBOSE) << "compiler::ReadProtosFromFileNames [success]";
+ return trace_info.value();
+ // TODO: protobufs have no move constructor. this might be inefficient?
+ });
+
+/*
+ return filenames
+ .map([](const std::string& filename) {
+ LOG(VERBOSE) << "compiler::ReadProtosFromFileNames " << filename << " [begin]";
+ std::optional<BinaryWireProtoT> maybe_proto =
+ BinaryWireProtoT::ReadFullyFromFile(filename);
+ if (!maybe_proto) {
+ LOG(ERROR) << "Failed to read file: " << filename;
+ }
+
+ std::unique_ptr<BinaryWireProtoT> ptr;
+ if (maybe_proto) {
+ ptr.reset(new BinaryWireProtoT{std::move(*maybe_proto)});
+ }
+ return ptr;
+ })
+ .filter([](const std::unique_ptr<BinaryWireProtoT>& proto) {
+ return proto != nullptr;
+ })
+ .map([](std::unique_ptr<BinaryWireProtoT>& proto) {
+ std::optional<ProtoT> t = proto->template MaybeUnserialize<ProtoT>();
+ if (!t) {
+ LOG(ERROR) << "Failed to parse protobuf: "; // TODO: filename.
+ }
+ return t;
+ })
+ .filter([](const std::optional<ProtoT>& proto) {
+ return proto.has_value();
+ })
+ .map([](std::optional<ProtoT> proto) -> ProtoT {
+ LOG(VERBOSE) << "compiler::ReadProtosFromFileNames [success]";
+ return std::move(proto.value());
+ // TODO: protobufs have no move constructor. this might be inefficient?
+ });
+ */
+}
+
+auto/*observable<PerfettoTracePtrInfo>*/ ReadPerfettoTraceProtos(
+ std::vector<CompilationInput> file_infos) {
+ auto filename_obs = rxcpp::observable<>::iterate(std::move(file_infos));
+ rxcpp::observable<PerfettoTracePtrInfo> obs =
+ ReadProtosFromFileNames<::perfetto::protos::Trace>(std::move(filename_obs));
+ return obs;
+}
+
+// A flattened data representation of an MmFileMap*FtraceEvent.
+// This representation is used for streaming processing.
+//
+// Note: Perfetto applies a 'union' over all possible fields on all possible devices
+// (and uses the max sizeof per-field).
+//
+// Since all protobuf fields are optional, fields not present on a particular device are always
+// null
+struct PageCacheFtraceEvent {
+ /*
+ * Ftrace buffer-specific
+ */
+ uint32_t cpu; // e.g. 0-7 for the cpu core number.
+
+ /*
+ * Ftrace-event general data
+ */
+
+ // Nanoseconds since an epoch.
+ // Epoch is configurable by writing into trace_clock.
+ // By default this timestamp is CPU local.
+ uint64_t timestamp;
+ // Kernel pid (do not confuse with userspace pid aka tgid)
+ uint32_t pid;
+
+ // Tagged by our code while parsing the ftraces:
+ uint64_t timestamp_relative; // timestamp relative to first ftrace within a Trace protobuf.
+ bool add_to_page_cache; // AddToPageCache=true, DeleteFromPageCache=false.
+
+ /*
+ * mm_filemap-specific data
+ *
+ * Fields are common:
+ * - MmFilemapAddToPageCacheFtraceEvent
+ * - MmFilemapDeleteFromPageCacheFtraceEvent
+ */
+ uint64_t pfn; // page frame number (physical) - null on some devices, e.g. marlin
+ uint64_t i_ino; // inode number (use in conjunction with s_dev)
+ uint64_t index; // offset into file: this is a multiple of the page size (usually 4096).
+ uint64_t s_dev; // (dev_t) device number
+ uint64_t page; // struct page*. - null on some devices, e.g. blueline.
+
+ Inode inode() const {
+ return Inode::FromDeviceAndInode(static_cast<dev_t>(s_dev),
+ static_cast<ino_t>(i_ino));
+ }
+};
+
+std::ostream& operator<<(std::ostream& os, const PageCacheFtraceEvent& e) {
+ os << "{";
+ os << "cpu:" << e.cpu << ",";
+ os << "timestamp:" << e.timestamp << ",";
+ os << "pid:" << e.pid << ",";
+ os << "timestamp_relative:" << e.timestamp_relative << ",";
+ os << "add_to_page_cache:" << e.add_to_page_cache << ",";
+ os << "pfn:" << e.pfn << ",";
+ os << "i_ino:" << e.i_ino << ",";
+ os << "index:" << e.index << ",";
+ os << "s_dev:" << e.s_dev << ",";
+ os << "page:" << e.page;
+ os << "}";
+
+ return os;
+}
+
+/*
+ * Gets the start timestamp.
+ *
+ * It is the minimium timestamp.
+ */
+std::optional<uint64_t> GetStartTimestamp(const ::perfetto::protos::Trace& trace) {
+ std::optional<uint64_t> timestamp_relative_start;
+ // Traverse each timestamp to get the minimium one.
+ for (const ::perfetto::protos::TracePacket& packet : trace.packet()) {
+ if (packet.has_timestamp()) {
+ timestamp_relative_start = timestamp_relative_start?
+ std::min(*timestamp_relative_start, packet.timestamp()) : packet.timestamp();
+ }
+ if (!packet.has_ftrace_events()) {
+ continue;
+ }
+ const ::perfetto::protos::FtraceEventBundle& ftrace_event_bundle =
+ packet.ftrace_events();
+ for (const ::perfetto::protos::FtraceEvent& event : ftrace_event_bundle.event()) {
+ if (event.has_timestamp()) {
+ timestamp_relative_start = timestamp_relative_start?
+ std::min(*timestamp_relative_start, event.timestamp()) : event.timestamp();
+ }
+ }
+ }
+ return timestamp_relative_start;
+}
+
+/*
+ * sample blueline output:
+ *
+ * $ adb shell cat /d/tracing/events/filemap/mm_filemap_add_to_page_cache/format
+ *
+ * name: mm_filemap_add_to_page_cache
+ * ID: 178
+ * format:
+ * field:unsigned short common_type; offset:0; size:2; signed:0;
+ * field:unsigned char common_flags; offset:2; size:1; signed:0;
+ * field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ * field:int common_pid; offset:4; size:4; signed:1;
+ *
+ * field:unsigned long pfn; offset:8; size:8; signed:0;
+ * field:unsigned long i_ino; offset:16; size:8; signed:0;
+ * field:unsigned long index; offset:24; size:8; signed:0;
+ * field:dev_t s_dev; offset:32; size:4; signed:0;
+ *
+ * print fmt: "dev %d:%d ino %lx page=%p pfn=%lu ofs=%lu", ((unsigned int) ((REC->s_dev) >> 20)),
+ * ((unsigned int) ((REC->s_dev) & ((1U << 20) - 1))), REC->i_ino,
+ * (((struct page *)(((0xffffffffffffffffUL) - ((1UL) << ((39) - 1)) + 1) -
+ * ((1UL) << ((39) - 12 - 1 + 6))) - (memstart_addr >> 12)) + (REC->pfn)),
+ * REC->pfn, REC->index << 12
+ */
+
+auto /*observable<PageCacheFtraceEvent>*/ SelectPageCacheFtraceEvents(
+ PerfettoTracePtrInfo trace_info) {
+ const ::perfetto::protos::Trace& trace = *(trace_info.trace_ptr);
+
+ constexpr bool kDebugFunction = true;
+
+ return rxcpp::observable<>::create<PageCacheFtraceEvent>(
+ [trace=std::move(trace), timestamp_limit_ns=trace_info.timestamp_limit_ns]
+ (rxcpp::subscriber<PageCacheFtraceEvent> sub) {
+ uint64_t timestamp = 0;
+ uint64_t timestamp_relative = 0;
+
+ std::optional<uint64_t> timestamp_relative_start = GetStartTimestamp(trace);
+ uint32_t cpu = 0;
+ uint32_t pid = 0;
+ bool add_to_page_cache = true;
+
+ auto on_next_page_cache_event = [&](const auto& mm_event) {
+ PageCacheFtraceEvent out;
+ out.timestamp = timestamp;
+ out.cpu = cpu;
+ out.pid = pid;
+
+ out.timestamp_relative = timestamp_relative;
+ out.add_to_page_cache = add_to_page_cache;
+
+ out.pfn = mm_event.pfn();
+ out.i_ino = mm_event.i_ino();
+ out.index = mm_event.index();
+ out.s_dev = mm_event.s_dev();
+ out.page = mm_event.page();
+
+ sub.on_next(std::move(out));
+ };
+
+ for (const ::perfetto::protos::TracePacket& packet : trace.packet()) {
+ // Break out of all loops if we are unsubscribed.
+ if (!sub.is_subscribed()) {
+ if (kDebugFunction) LOG(VERBOSE) << "compiler::SelectPageCacheFtraceEvents unsubscribe";
+ return;
+ }
+
+ if (kDebugFunction) LOG(VERBOSE) << "compiler::SelectPageCacheFtraceEvents TracePacket";
+
+ if (packet.has_timestamp()) {
+ timestamp_relative_start = timestamp_relative_start.value_or(packet.timestamp());
+ timestamp = packet.timestamp(); // XX: should we call 'has_timestamp()' ?
+ } else {
+ timestamp = 0;
+ }
+
+ if (packet.has_ftrace_events()) {
+ const ::perfetto::protos::FtraceEventBundle& ftrace_event_bundle =
+ packet.ftrace_events();
+
+ cpu = ftrace_event_bundle.cpu(); // XX: has_cpu ?
+
+ for (const ::perfetto::protos::FtraceEvent& event : ftrace_event_bundle.event()) {
+ // Break out of all loops if we are unsubscribed.
+ if (!sub.is_subscribed()) {
+ return;
+ }
+
+ if (event.has_timestamp()) {
+ timestamp = event.timestamp();
+ if(timestamp > timestamp_limit_ns) {
+ LOG(VERBOSE) << "The timestamp is " << timestamp <<
+ ", which exceeds the limit "<< timestamp_limit_ns;
+ continue;
+ }
+ } else {
+ DCHECK(packet.has_timestamp() == false)
+ << "Timestamp in outer packet but not inner packet";
+ // XX: use timestamp from the perfetto TracePacket ???
+ // REVIEWERS: not sure if this is ok, does it use the same clock source and
+ // is the packet data going to be the same clock sample as the Ftrace event?
+ }
+
+ if (timestamp_relative_start){
+ timestamp_relative = timestamp - *timestamp_relative_start;
+ } else {
+ timestamp_relative = 0;
+ }
+
+ pid = event.pid(); // XX: has_pid ?
+
+ if (event.has_mm_filemap_add_to_page_cache()) {
+ add_to_page_cache = true;
+
+ const ::perfetto::protos::MmFilemapAddToPageCacheFtraceEvent& mm_event =
+ event.mm_filemap_add_to_page_cache();
+
+ on_next_page_cache_event(mm_event);
+ } else if (event.has_mm_filemap_delete_from_page_cache()) {
+ add_to_page_cache = false;
+
+ const ::perfetto::protos::MmFilemapDeleteFromPageCacheFtraceEvent& mm_event =
+ event.mm_filemap_delete_from_page_cache();
+
+ on_next_page_cache_event(mm_event);
+ }
+ }
+ } else {
+ if (kDebugFunction) {
+ LOG(VERBOSE) << "compiler::SelectPageCacheFtraceEvents no ftrace event bundle";
+ }
+ }
+ }
+
+ if (kDebugFunction) {
+ LOG(VERBOSE) << "compiler::SelectPageCacheFtraceEvents#on_completed";
+ }
+
+ // Let subscriber know there are no more items.
+ sub.on_completed();
+ });
+}
+
+auto /*observable<Inode*/ SelectDistinctInodesFromTraces(
+ rxcpp::observable<PerfettoTracePtrInfo> traces) {
+ // Emit only unique (s_dev, i_ino) pairs from all Trace protos.
+ auto obs = traces
+ .flat_map([](PerfettoTracePtrInfo trace) {
+ rxcpp::observable<PageCacheFtraceEvent> obs = SelectPageCacheFtraceEvents(std::move(trace));
+ // FIXME: dont check this in
+ // return obs;
+ //return obs.take(100); // for faster development
+ return obs;
+ }) // TODO: Upstream bug? using []()::perfetto::protos::Trace&) causes a compilation error.
+ .map([](const PageCacheFtraceEvent& event) -> Inode {
+ return Inode::FromDeviceAndInode(static_cast<dev_t>(event.s_dev),
+ static_cast<ino_t>(event.i_ino));
+ })
+ .tap([](const Inode& inode) {
+ LOG(VERBOSE) << "SelectDistinctInodesFromTraces (pre-distinct): " << inode;
+ })
+ .distinct() // observable<Inode>*/
+ ;
+
+ return obs;
+}
+// TODO: static assert checks for convertible return values.
+
+auto/*observable<InodeResult>*/ ResolveInodesToFileNames(
+ rxcpp::observable<Inode> inodes,
+ inode2filename::InodeResolverDependencies dependencies) {
+ std::shared_ptr<inode2filename::InodeResolver> inode_resolver =
+ inode2filename::InodeResolver::Create(std::move(dependencies));
+ return inode_resolver->FindFilenamesFromInodes(std::move(inodes));
+}
+
+using InodeMap = std::unordered_map<Inode, std::string /*filename*/>;
+auto /*just observable<InodeMap>*/ ReduceResolvedInodesToMap(
+ rxcpp::observable<InodeResult> inode_results) {
+ return inode_results.reduce(
+ InodeMap{},
+ [](InodeMap m, InodeResult result) {
+ if (result) {
+ LOG(VERBOSE) << "compiler::ReduceResolvedInodesToMap insert " << result;
+ m.insert({std::move(result.inode), std::move(result.data.value())});
+ } else {
+ // TODO: side stats for how many of these are failed to resolve?
+ LOG(WARNING) << "compiler: Failed to resolve inode, " << result;
+ }
+ return m;
+ },
+ [](InodeMap m) {
+ return m; // TODO: use an identity function
+ }); // emits exactly 1 InodeMap value.
+}
+
+struct ResolvedPageCacheFtraceEvent {
+ std::string filename;
+ PageCacheFtraceEvent event;
+};
+
+std::ostream& operator<<(std::ostream& os, const ResolvedPageCacheFtraceEvent& e) {
+ os << "{";
+ os << "filename:\"" << e.filename << "\",";
+ os << e.event;
+ os << "}";
+
+ return os;
+}
+
+struct CombinedState {
+ CombinedState() = default;
+ explicit CombinedState(InodeMap inode_map) : inode_map{std::move(inode_map)} {}
+ explicit CombinedState(PageCacheFtraceEvent event) : ftrace_event{std::move(event)} {}
+
+ CombinedState(InodeMap inode_map, PageCacheFtraceEvent event)
+ : inode_map(std::move(inode_map)),
+ ftrace_event{std::move(event)} {}
+
+ std::optional<InodeMap> inode_map;
+ std::optional<PageCacheFtraceEvent> ftrace_event;
+
+ bool HasAll() const {
+ return inode_map.has_value() && ftrace_event.has_value();
+ }
+
+ const InodeMap& GetInodeMap() const {
+ DCHECK(HasAll());
+ return inode_map.value();
+ }
+
+ InodeMap& GetInodeMap() {
+ DCHECK(HasAll());
+ return inode_map.value();
+ }
+
+ const PageCacheFtraceEvent& GetEvent() const {
+ DCHECK(HasAll());
+ return ftrace_event.value();
+ }
+
+ PageCacheFtraceEvent& GetEvent() {
+ DCHECK(HasAll());
+ return ftrace_event.value();
+ }
+
+ void Merge(CombinedState&& other) {
+ if (other.inode_map) {
+ inode_map = std::move(other.inode_map);
+ }
+ if (other.ftrace_event) {
+ ftrace_event = std::move(other.ftrace_event);
+ }
+ }
+};
+
+std::ostream& operator<<(std::ostream& os, const CombinedState& s) {
+ os << "CombinedState{inode_map:";
+ if (s.inode_map) {
+ os << "|sz=" << (s.inode_map.value().size()) << "|";
+ } else {
+ os << "(null)";
+ }
+ os << ",event:";
+ if (s.ftrace_event) {
+ //os << s.ftrace_event.value().timestamp << "ns";
+ os << s.ftrace_event.value();
+ } else {
+ os << "(null)";
+ }
+ os << "}";
+ return os;
+}
+
+auto/*observable<ResolvedPageCacheFtraceEvent>*/ ResolvePageCacheEntriesFromProtos(
+ rxcpp::observable<PerfettoTracePtrInfo> traces,
+ inode2filename::InodeResolverDependencies dependencies) {
+
+ // 1st chain = emits exactly 1 InodeMap.
+
+ // [proto, proto, proto...] -> [inode, inode, inode, ...]
+ auto/*observable<Inode>*/ distinct_inodes = SelectDistinctInodesFromTraces(traces);
+ rxcpp::observable<Inode> distinct_inodes_obs = distinct_inodes.as_dynamic();
+ // [inode, inode, inode, ...] -> [(inode, {filename|error}), ...]
+ auto/*observable<InodeResult>*/ inode_names = ResolveInodesToFileNames(distinct_inodes_obs,
+ std::move(dependencies));
+ // rxcpp has no 'join' operators, so do a manual join with concat.
+ auto/*observable<InodeMap>*/ inode_name_map = ReduceResolvedInodesToMap(inode_names);
+
+ // 2nd chain = emits all PageCacheFtraceEvent
+ auto/*observable<PageCacheFtraceEvent>*/ page_cache_ftrace_events = traces
+ .flat_map([](PerfettoTracePtrInfo trace) {
+ rxcpp::observable<PageCacheFtraceEvent> obs = SelectPageCacheFtraceEvents(std::move(trace));
+ return obs;
+ });
+
+ auto inode_name_map_precombine = inode_name_map
+ .map([](InodeMap inode_map) {
+ LOG(VERBOSE) << "compiler::ResolvePageCacheEntriesFromProtos#inode_name_map_precombine ";
+ return CombinedState{std::move(inode_map)};
+ });
+
+ auto page_cache_ftrace_events_precombine = page_cache_ftrace_events
+ .map([](PageCacheFtraceEvent event) {
+ LOG(VERBOSE)
+ << "compiler::ResolvePageCacheEntriesFromProtos#page_cache_ftrace_events_precombine "
+ << event;
+ return CombinedState{std::move(event)};
+ });
+
+ // Combine 1st+2nd chain.
+ //
+ // concat subscribes to each observable, waiting until its completed, before subscribing
+ // to the next observable and waiting again.
+ //
+ // During all this, every #on_next is immediately forwarded to the downstream observables.
+ // In our case, we want to block until InodeNameMap is ready, and re-iterate all ftrace events.
+ auto/*observable<ResolvedPageCacheFtraceEvent>*/ resolved_events = inode_name_map_precombine
+ .concat(page_cache_ftrace_events_precombine)
+ .scan(CombinedState{},
+ [](CombinedState current_state, CombinedState delta_state) {
+ LOG(VERBOSE) << "compiler::ResolvePageCacheEntriesFromProtos#scan "
+ << "current=" << current_state << ","
+ << "delta=" << delta_state;
+ // IT0 = (,) + (InodeMap,)
+ // IT1 = (InodeMap,) + (,Event)
+ // IT2..N = (InodeMap,Event1) + (,Event2)
+ current_state.Merge(std::move(delta_state));
+ return current_state;
+ })
+ .filter([](const CombinedState& state) {
+ return state.HasAll();
+ })
+ .map([](CombinedState& state) -> std::optional<ResolvedPageCacheFtraceEvent> {
+ PageCacheFtraceEvent& event = state.GetEvent();
+ const InodeMap& inode_map = state.GetInodeMap();
+
+ auto it = inode_map.find(event.inode());
+ if (it != inode_map.end()) {
+ std::string filename = it->second;
+ LOG(VERBOSE) << "compiler::ResolvePageCacheEntriesFromProtos combine_latest " << event;
+ return ResolvedPageCacheFtraceEvent{std::move(filename), std::move(event)};
+ } else {
+ LOG(ERROR) << "compiler: FtraceEvent's inode did not have resolved filename: " << event;
+ return std::nullopt;
+ }
+ })
+ .filter(
+ [](std::optional<ResolvedPageCacheFtraceEvent> maybe_event) {
+ return maybe_event.has_value();
+ })
+ .map([](std::optional<ResolvedPageCacheFtraceEvent> maybe_event) {
+ return std::move(maybe_event.value());
+ });
+ // -> observable<ResolvedPageCacheFtraceEvent>
+
+ return resolved_events;
+}
+
+namespace detail {
+bool multiless_one(const std::string& a, const std::string& b) {
+ return std::lexicographical_compare(a.begin(), a.end(),
+ b.begin(), b.end());
+}
+
+template <typename T>
+constexpr bool multiless_one(T&& a, T&& b) { // a < b
+ using std::less; // ADL
+ return less<std::decay_t<T>>{}(std::forward<T>(a), std::forward<T>(b));
+}
+
+constexpr bool multiless() {
+ return false; // [] < [] is always false.
+}
+
+template <typename T, typename ... Args>
+constexpr bool multiless(T&& a, T&& b, Args&&... args) {
+ if (a != b) {
+ return multiless_one(std::forward<T>(a), std::forward<T>(b));
+ } else {
+ return multiless(std::forward<Args>(args)...);
+ }
+}
+
+} // namespace detail
+
+// Return [A0...An] < [B0...Bn] ; vector-like scalar comparison of each field.
+// Arguments are passed in the order A0,B0,A1,B1,...,An,Bn.
+template <typename ... Args>
+constexpr bool multiless(Args&&... args) {
+ return detail::multiless(std::forward<Args>(args)...);
+}
+
+struct CompilerPageCacheEvent {
+ std::string filename;
+ uint64_t timestamp_relative; // use relative timestamp because absolute values aren't comparable
+ // across different trace protos.
+ // relative timestamps can be said to be 'approximately' comparable.
+ // assuming we compare the same application startup's trace times.
+ bool add_to_page_cache; // AddToPageCache=true, DeleteFromPageCache=false.
+ uint64_t index; // offset into file: this is a multiple of the page size (usually 4096).
+
+ // All other data from the ftrace is dropped because we don't currently use it in the
+ // compiler algorithms.
+
+ CompilerPageCacheEvent() = default;
+ CompilerPageCacheEvent(const ResolvedPageCacheFtraceEvent& resolved)
+ : CompilerPageCacheEvent(resolved.filename, resolved.event) {
+ }
+
+ CompilerPageCacheEvent(ResolvedPageCacheFtraceEvent&& resolved)
+ : CompilerPageCacheEvent(std::move(resolved.filename), std::move(resolved.event)) {
+ }
+
+ // Compare all fields (except the timestamp field).
+ static bool LessIgnoringTimestamp(const CompilerPageCacheEvent& a,
+ const CompilerPageCacheEvent& b) {
+ return multiless(a.filename, b.filename,
+ a.add_to_page_cache, b.add_to_page_cache,
+ a.index, b.index);
+ }
+
+ // Compare all fields. Timestamps get highest precedence.
+ bool operator<(const CompilerPageCacheEvent& rhs) const {
+ return multiless(timestamp_relative, rhs.timestamp_relative,
+ filename, rhs.filename,
+ add_to_page_cache, rhs.add_to_page_cache,
+ index, rhs.index);
+ }
+
+ private:
+ CompilerPageCacheEvent(std::string filename, const PageCacheFtraceEvent& event)
+ : filename(std::move(filename)),
+ timestamp_relative(event.timestamp_relative),
+ add_to_page_cache(event.add_to_page_cache),
+ index(event.index) {
+ }
+};
+
+std::ostream& operator<<(std::ostream& os, const CompilerPageCacheEvent& e) {
+ os << "{";
+ os << "filename:\"" << e.filename << "\",";
+ os << "timestamp:" << e.timestamp_relative << ",";
+ os << "add_to_page_cache:" << e.add_to_page_cache << ",";
+ os << "index:" << e.index;
+ os << "}";
+ return os;
+}
+
+// Filter an observable chain of 'ResolvedPageCacheFtraceEvent'
+// into an observable chain of 'ResolvedPageCacheFtraceEvent'.
+//
+// Any items emitted by the input chain that match the regular expression
+// specified by blacklist_filter are not emitted into the output chain.
+auto/*observable<ResolvedPageCacheFtraceEvent>*/ ApplyBlacklistToPageCacheEvents(
+ rxcpp::observable<ResolvedPageCacheFtraceEvent> resolved_events,
+ std::optional<std::string> blacklist_filter) {
+ bool has_re = blacklist_filter.has_value();
+ // default regex engine is ecmascript.
+ std::regex reg_exp{blacklist_filter ? *blacklist_filter : std::string("")};
+
+ return resolved_events.filter(
+ [reg_exp, has_re](const ResolvedPageCacheFtraceEvent& event) {
+ if (!has_re) {
+ return true;
+ }
+ // Remove any entries that match the regex in --blacklist-filter/-bf.
+ bool res = std::regex_search(event.filename, reg_exp);
+ if (res) {
+ LOG(VERBOSE) << "Blacklist filter removed '" << event.filename << "' from chain.";
+ }
+ return !res;
+ });
+}
+
+// Compile an observable chain of 'ResolvedPageCacheFtraceEvent' into
+// an observable chain of distinct, timestamp-ordered, CompilerPageCacheEvent.
+//
+// This is a reducing operation: No items are emitted until resolved_events is completed.
+auto/*observable<CompilerPageCacheEvent>*/ CompilePageCacheEvents(
+ rxcpp::observable<ResolvedPageCacheFtraceEvent> resolved_events) {
+
+ struct CompilerPageCacheEventIgnoringTimestampLess {
+ bool operator()(const CompilerPageCacheEvent& lhs,
+ const CompilerPageCacheEvent& rhs) const {
+ return CompilerPageCacheEvent::LessIgnoringTimestamp(lhs, rhs);
+ }
+ };
+
+ // Greedy O(N) compilation algorithm.
+ //
+ // This produces an inoptimal result (e.g. a small timestamp
+ // that might occur only 1% of the time nevertheless wins out), but the
+ // algorithm itself is quite simple, and doesn't require any heuristic tuning.
+
+ // First pass: *Merge* into set that ignores the timestamp value for order, but retains
+ // the smallest timestamp value if the same key is re-inserted.
+ using IgnoreTimestampForOrderingSet =
+ std::set<CompilerPageCacheEvent, CompilerPageCacheEventIgnoringTimestampLess>;
+ // Second pass: *Sort* data by smallest timestamp first.
+ using CompilerPageCacheEventSet =
+ std::set<CompilerPageCacheEvent>;
+
+ return resolved_events
+ .map(
+ [](ResolvedPageCacheFtraceEvent event) {
+ // Drop all the extra metadata like pid, cpu, etc.
+ // When we merge we could keep a list of the original data, but there is no advantage
+ // to doing so.
+ return CompilerPageCacheEvent{std::move(event)};
+ }
+ )
+ .reduce(
+ IgnoreTimestampForOrderingSet{},
+ [](IgnoreTimestampForOrderingSet set, CompilerPageCacheEvent event) {
+ // Add each event to the set, keying by everything but the timestamp.
+ // If the key is already inserted, re-insert with the smaller timestamp value.
+ auto it = set.find(event);
+
+ if (it == set.end()) {
+ // Need to insert new element.
+ set.insert(std::move(event));
+ } else if (it->timestamp_relative > event.timestamp_relative) {
+ // Replace existing element: the new element has a smaller timestamp.
+ it = set.erase(it);
+ // Amortized O(1) time if insertion happens in the position before the hint.
+ set.insert(it, std::move(event));
+ } // else: Skip insertion. Element already present with the minimum timestamp.
+
+ return set;
+ },
+ [](IgnoreTimestampForOrderingSet set) {
+ // Extract all elements from 'set', re-insert into 'ts_set'.
+ // The values are now ordered by timestamp (and then the rest of the fields).
+ CompilerPageCacheEventSet ts_set;
+ ts_set.merge(std::move(set));
+
+
+ std::shared_ptr<CompilerPageCacheEventSet> final_set{
+ new CompilerPageCacheEventSet{std::move(ts_set)}};
+ return final_set;
+ // return ts_set;
+ }) // observable<CompilerPageCacheEventSet> (just)
+ .flat_map(
+ [](std::shared_ptr<CompilerPageCacheEventSet> final_set) {
+ // TODO: flat_map seems to make a copy of the parameter _every single iteration_
+ // without the shared_ptr it would just make a copy of the set every time it went
+ // through the iterate function.
+ // Causing absurdly slow compile times x1000 slower than we wanted.
+ // TODO: file a bug upstream and/or fix upstream.
+ CompilerPageCacheEventSet& ts_set = *final_set;
+ // [](CompilerPageCacheEventSet& ts_set) {
+ LOG(DEBUG) << "compiler: Merge-pass completed (" << ts_set.size() << " entries).";
+ //return rxcpp::sources::iterate(std::move(ts_set));
+ return rxcpp::sources::iterate(ts_set).map([](CompilerPageCacheEvent e) { return e; });
+ }
+ ); // observable<CompilerPageCacheEvent>
+}
+
+/** Makes a vector of info that includes filename and timestamp limit. */
+std::vector<CompilationInput> MakeCompilationInputs(
+ std::vector<std::string> input_file_names,
+ std::vector<uint64_t> timestamp_limit_ns){
+ // If the timestamp limit is empty, set the limit to max value
+ // for each trace file.
+ if (timestamp_limit_ns.empty()) {
+ for (size_t i = 0; i < input_file_names.size(); i++) {
+ timestamp_limit_ns.push_back(std::numeric_limits<uint64_t>::max());
+ }
+ }
+ DCHECK_EQ(input_file_names.size(), timestamp_limit_ns.size());
+ std::vector<CompilationInput> file_infos;
+ for (size_t i = 0; i < input_file_names.size(); i++) {
+ file_infos.push_back({input_file_names[i], timestamp_limit_ns[i]});
+ }
+ return file_infos;
+}
+
+bool PerformCompilation(std::vector<CompilationInput> perfetto_traces,
+ std::string output_file_name,
+ bool output_proto,
+ std::optional<std::string> blacklist_filter,
+ inode2filename::InodeResolverDependencies dependencies) {
+ auto trace_protos = ReadPerfettoTraceProtos(std::move(perfetto_traces));
+ auto resolved_events = ResolvePageCacheEntriesFromProtos(std::move(trace_protos),
+ std::move(dependencies));
+ auto filtered_events =
+ ApplyBlacklistToPageCacheEvents(std::move(resolved_events), blacklist_filter);
+ auto compiled_events = CompilePageCacheEvents(std::move(filtered_events));
+
+ std::ofstream ofs;
+ if (!output_file_name.empty()) {
+
+ if (!output_proto) {
+ ofs.open(output_file_name);
+
+ if (!ofs) {
+ LOG(ERROR) << "compiler: Failed to open output file for writing: " << output_file_name;
+ return false;
+ }
+ }
+ }
+
+ auto trace_file_proto = serialize::ArenaPtr<serialize::proto::TraceFile>::Make();
+
+ // Fast lookup of filename -> FileIndex id.
+ std::unordered_map<std::string, int64_t /*file handle id*/> file_path_map;
+ int64_t file_handle_id = 0;
+
+ int counter = 0;
+ compiled_events
+ // .as_blocking()
+ .tap([&](CompilerPageCacheEvent& event) {
+ if (!output_proto) {
+ return;
+ }
+
+ if (!event.add_to_page_cache) {
+ // Skip DeleteFromPageCache events, they are only used for intermediate.
+ return;
+ }
+
+ DCHECK(trace_file_proto->mutable_index() != nullptr);
+ serialize::proto::TraceFileIndex& index = *trace_file_proto->mutable_index();
+ int64_t file_handle;
+
+ // Add TraceFileIndexEntry if it doesn't exist.
+
+ auto it = file_path_map.find(event.filename);
+ if (it == file_path_map.end()) {
+ file_handle = file_handle_id++;
+ file_path_map[event.filename] = file_handle;
+
+ serialize::proto::TraceFileIndexEntry* entry = index.add_entries();
+ DCHECK(entry != nullptr);
+ entry->set_id(file_handle);
+ entry->set_file_name(event.filename);
+
+ if (kIsDebugBuild) {
+ int i = static_cast<int>(file_handle);
+ const serialize::proto::TraceFileIndexEntry& entry_ex = index.entries(i);
+ DCHECK_EQ(entry->id(), entry_ex.id());
+ DCHECK_EQ(entry->file_name(), entry_ex.file_name());
+ }
+ } else {
+ file_handle = it->second;
+ }
+ int kPageSize = 4096; // TODO: don't hardcode the page size.
+
+ // Add TraceFileEntry.
+ DCHECK(trace_file_proto->mutable_list() != nullptr);
+ serialize::proto::TraceFileEntry* entry = trace_file_proto->mutable_list()->add_entries();
+ DCHECK(entry != nullptr);
+
+ entry->set_index_id(file_handle);
+ // Page index -> file offset in bytes.
+ entry->set_file_offset(static_cast<int64_t>(event.index) * kPageSize);
+ entry->set_file_length(kPageSize);
+ })
+ .subscribe([&](CompilerPageCacheEvent event) {
+ if (!output_proto) {
+ if (output_file_name.empty()) {
+ LOG(INFO) << "CompilerPageCacheEvent" << event << std::endl;
+ } else {
+ ofs << event << "\n"; // TODO: write in proto format instead.
+ }
+ }
+ ++counter;
+ });
+
+ if (output_proto) {
+ LOG(DEBUG) << "compiler: WriteFully to begin into " << output_file_name;
+ ::google::protobuf::MessageLite& message = *trace_file_proto.get();
+ if (auto res = serialize::ProtobufIO::WriteFully(message, output_file_name); !res) {
+ errno = res.error();
+ PLOG(ERROR) << "compiler: Failed to write protobuf to file: " << output_file_name;
+ return false;
+ } else {
+ LOG(INFO) << "compiler: Wrote protobuf " << output_file_name;
+ }
+ }
+
+ LOG(DEBUG) << "compiler: Compilation completed (" << counter << " events).";
+
+ return true;
+}
+
+} // namespace iorap::compiler
diff --git a/src/compiler/compiler.h b/src/compiler/compiler.h
new file mode 100644
index 0000000..1fa9ff1
--- /dev/null
+++ b/src/compiler/compiler.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2019 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 IORAP_SRC_COMPILER_COMPILER_H_
+#define IORAP_SRC_COMPILER_COMPILER_H_
+
+#include "inode2filename/inode_resolver.h"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace iorap::compiler {
+struct CompilationInput {
+ /* The name of the perfetto trace. */
+ std::string filename;
+ /*
+ * The timestamp limit of the trace.
+ * It's used to truncate the trace file.
+ */
+ uint64_t timestamp_limit_ns;
+};
+
+// Compile one or more perfetto TracePacket protobufs that are stored on the filesystem
+// by the filenames given with `input_file_names` and timestamp limit given with
+// timestamp_limit_ns.
+//
+// For each input file name and timestamp limit, ignore any events from the input that
+// exceed the associated timestamp limit.
+//
+// If blacklist_filter is non-null, ignore any file entries whose file path matches the
+// regular expression in blacklist_filter.
+//
+// The result is stored into the file path specified by `output_file_name`.
+//
+// This is a blocking function which returns only when compilation finishes. The return value
+// determines success/failure.
+//
+// Operation is transactional -- that is if there is a failure, `output_file_name` is untouched.
+bool PerformCompilation(std::vector<iorap::compiler::CompilationInput> perfetto_traces,
+ std::string output_file_name,
+ bool output_proto,
+ std::optional<std::string> blacklist_filter,
+ inode2filename::InodeResolverDependencies dependencies);
+
+// The size and order of timestamp_limit_ns should match that of
+// input_file_names, if not empty.
+// If timestamp_limit_ns is empty, will use the max uint64_t.
+std::vector<CompilationInput> MakeCompilationInputs(
+ std::vector<std::string> input_file_names,
+ std::vector<uint64_t> timestamp_limit_ns);
+}
+
+#endif // IORAP_SRC_COMPILER_COMPILER_H_
diff --git a/src/compiler/main.cc b/src/compiler/main.cc
new file mode 100644
index 0000000..1ecfaed
--- /dev/null
+++ b/src/compiler/main.cc
@@ -0,0 +1,207 @@
+// Copyright (C) 2019 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 "common/cmd_utils.h"
+#include "common/debug.h"
+#include "compiler/compiler.h"
+#include "inode2filename/inode_resolver.h"
+
+#include <android-base/parseint.h>
+#include <android-base/logging.h>
+
+#include <iostream>
+#include <limits>
+#include <optional>
+#include <string>
+
+#if defined(IORAP_COMPILER_MAIN)
+
+namespace iorap::compiler {
+
+void Usage(char** argv) {
+ std::cerr << "Usage: " << argv[0] << " [--output-proto=output.pb] input1.pb [input2.pb ...]" << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Request a compilation of multiple inputs (format: PerfettoTraceProto)." << std::endl;
+ std::cerr << " The result is a TraceFile, representing a merged compiled trace with inodes resolved." << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Optional flags:" << std::endl;
+ std::cerr << " --help,-h Print this Usage." << std::endl;
+ std::cerr << " --blacklist-filter,-bf Specify regex acting as a blacklist filter." << std::endl;
+ std::cerr << " Filepaths matching this regex are removed from the output file." << std::endl;
+ std::cerr << " --output-text,-ot Output ascii text instead of protobuf (default off)." << std::endl;
+ std::cerr << " --output-proto $,-op $ TraceFile tracebuffer output file (default stdout)." << std::endl;
+ std::cerr << " --inode-textcache $,-it $ Resolve inode->filename from textcache (disables diskscan)." << std::endl;
+ std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
+ std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
+ std::cerr << " --timestamp_limit_ns,-tl Set the limit timestamp in nanoseconds for the compiled trace. "
+ "The order and size of the timestamp should match that of "
+ "the input trace files. If not specified at all, All of"
+ "the timestamps are set to max."<< std::endl;
+ exit(1);
+}
+
+int Main(int argc, char** argv) {
+ android::base::InitLogging(argv);
+ android::base::SetLogger(android::base::StderrLogger);
+
+ bool wait_for_keystroke = false;
+ bool enable_verbose = false;
+
+ std::optional<std::string> arg_blacklist_filter;
+ std::string arg_output_proto;
+ bool arg_output_text = false;
+ std::optional<std::string> arg_inode_textcache;
+
+ std::vector<uint64_t> timestamp_limit_ns;
+
+ if (argc == 1) {
+ // Need at least 1 input file to do anything.
+ Usage(argv);
+ }
+
+ std::vector<std::string> arg_input_filenames;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+ bool has_arg_next = (arg+1)<argc;
+ std::string arg_next = has_arg_next ? argv[arg+1] : "";
+
+ if (argstr == "--help" || argstr == "-h") {
+ Usage(argv);
+ } else if (argstr == "--output-proto" || argstr == "-op") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --output-proto <value>" << std::endl;
+ return 1;
+ }
+ arg_output_proto = arg_next;
+ ++arg;
+ } else if (argstr == "--output-text" || argstr == "-ot") {
+ arg_output_text = true;
+ } else if (argstr == "--inode-textcache" || argstr == "-it") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --inode-textcache <value>" << std::endl;
+ return 1;
+ }
+ arg_inode_textcache = arg_next;
+ ++arg;
+ } else if (argstr == "--blacklist-filter" || argstr == "-bf") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --blacklist-filter <value>" << std::endl;
+ return 1;
+ }
+ arg_blacklist_filter = arg_next;
+ ++arg;
+ }
+ else if (argstr == "--verbose" || argstr == "-v") {
+ enable_verbose = true;
+ } else if (argstr == "--wait" || argstr == "-w") {
+ wait_for_keystroke = true;
+ } else if (argstr == "--timestamp_limit_ns" || argstr == "-tl") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --timestamp_limit_ns <value>" << std::endl;
+ return 1;
+ }
+ uint64_t timestamp;
+ if (!::android::base::ParseUint<uint64_t>(arg_next, &timestamp)) {
+ std::cerr << "Invalid --timestamp-limit-ns "<< arg_next << std::endl;
+ return 1;
+ }
+ timestamp_limit_ns.push_back(timestamp);
+ ++arg;
+ } else {
+ arg_input_filenames.push_back(argstr);
+ }
+ }
+
+ if (!timestamp_limit_ns.empty() &&
+ timestamp_limit_ns.size() != arg_input_filenames.size()) {
+ std::cerr << "The size of timestamp limits doesn't match the size of input files."
+ << std::endl;
+ return 1;
+ }
+
+ if (enable_verbose) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+ LOG(VERBOSE) << "Verbose check";
+ LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::DEBUG);
+ }
+
+ // Useful to attach a debugger...
+ // 1) $> iorap.cmd.compiler -w <args>
+ // 2) $> gdbclient <pid>
+ if (wait_for_keystroke) {
+ LOG(INFO) << "Self pid: " << getpid();
+ LOG(INFO) << "Press any key to continue...";
+ std::cin >> wait_for_keystroke;
+ }
+
+ auto system_call = std::make_unique<SystemCallImpl>();
+
+ using namespace inode2filename;
+
+ InodeResolverDependencies ir_dependencies;
+ // Passed from command-line.
+ if (arg_inode_textcache) {
+ ir_dependencies.data_source = DataSourceKind::kTextCache;
+ ir_dependencies.text_cache_filename = arg_inode_textcache;
+ ir_dependencies.verify = VerifyKind::kNone; // required for determinism.
+ } else {
+ ir_dependencies.data_source = DataSourceKind::kDiskScan;
+ LOG(WARNING) << "--inode-textcache unspecified. "
+ << "Inodes will be resolved by scanning the disk, which makes compilation "
+ << "non-deterministic.";
+ }
+ // TODO: add command line.
+ ir_dependencies.root_directories.push_back("/system");
+ ir_dependencies.root_directories.push_back("/apex");
+ ir_dependencies.root_directories.push_back("/data");
+ ir_dependencies.root_directories.push_back("/vendor");
+ ir_dependencies.root_directories.push_back("/product");
+ ir_dependencies.root_directories.push_back("/metadata");
+ // Hardcoded.
+ if (iorap::common::GetBoolEnvOrProperty("iorap.inode2filename.out_of_process", true)) {
+ ir_dependencies.process_mode = ProcessMode::kOutOfProcessIpc;
+ } else {
+ ir_dependencies.process_mode = ProcessMode::kInProcessDirect;
+ }
+ ir_dependencies.system_call = /*borrowed*/system_call.get();
+
+ int return_code = 0;
+ std::vector<CompilationInput> perfetto_traces =
+ MakeCompilationInputs(arg_input_filenames, timestamp_limit_ns);
+ return_code =
+ !PerformCompilation(std::move(perfetto_traces),
+ std::move(arg_output_proto),
+ !arg_output_text,
+ arg_blacklist_filter,
+ std::move(ir_dependencies));
+
+ // Uncomment this if we want to leave the process around to inspect it from adb shell.
+ // sleep(100000);
+
+ // 0 -> successfully wrote the proto out to file.
+ // 1 -> failed along the way (#on_error and also see the error logs).
+ return return_code;
+}
+
+} // namespace iorap::compiler
+
+int main(int argc, char** argv) {
+ return ::iorap::compiler::Main(argc, argv);
+}
+
+#endif // IORAP_COMPILER_MAIN
diff --git a/src/db/app_component_name.h b/src/db/app_component_name.h
new file mode 100644
index 0000000..e364163
--- /dev/null
+++ b/src/db/app_component_name.h
@@ -0,0 +1,130 @@
+// Copyright (C) 2019 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 IORAP_SRC_DB_APP_COMPONENT_NAME_H_
+#define IORAP_SRC_DB_APP_COMPONENT_NAME_H_
+
+namespace iorap::db {
+
+struct AppComponentName {
+ std::string package;
+ std::string activity_name;
+
+ // Turns the activity name to the fully qualified name.
+ // For example, if the activity name is ".MainActivity" and the package is
+ // foo.bar. Then the fully qualified name is foo.bar.MainActivity.
+ AppComponentName Canonicalize() const {
+ if (!activity_name.empty() && activity_name[0] == '.') {
+ return {package, package + activity_name};
+ }
+ return {package, activity_name};
+ }
+
+ static bool HasAppComponentName(const std::string& s) {
+ return s.find('/') != std::string::npos;
+ }
+
+ // "com.foo.bar/.A" -> {"com.foo.bar", ".A"}
+ static AppComponentName FromString(const std::string& s) {
+ constexpr const char delimiter = '/';
+
+ if (!HasAppComponentName(s)) {
+ return {std::move(s), ""};
+ }
+
+ std::string package = s.substr(0, s.find(delimiter));
+
+ std::string activity_name = s;
+ activity_name.erase(0, s.find(delimiter) + sizeof(delimiter));
+
+ return {std::move(package), std::move(activity_name)};
+ }
+
+ // {"com.foo.bar", ".A"} -> "com.foo.bar/.A"
+ std::string ToString() const {
+ return package + "/" + activity_name;
+ }
+
+ /*
+ * '/' is encoded into %2F
+ * '%' is encoded into %25
+ *
+ * This allows the component name to be be used as a file name
+ * ('/' is illegal due to being a path separator) with minimal
+ * munging.
+ */
+
+ // "com.foo.bar%2F.A%25" -> {"com.foo.bar", ".A%"}
+ static AppComponentName FromUrlEncodedString(const std::string& s) {
+ std::string cpy = s;
+ Replace(cpy, "%2F", "/");
+ Replace(cpy, "%25", "%");
+
+ return FromString(cpy);
+ }
+
+ // {"com.foo.bar", ".A%"} -> "com.foo.bar%2F.A%25"
+ std::string ToUrlEncodedString() const {
+ std::string s = ToString();
+ Replace(s, "%", "%25");
+ Replace(s, "/", "%2F");
+ return s;
+ }
+
+ /*
+ * '/' is encoded into @@
+ * '%' is encoded into ^^
+ *
+ * Two purpose:
+ * 1. This allows the package name to be used as a file name
+ * ('/' is illegal due to being a path separator) with minimal
+ * munging.
+ * 2. This allows the package name to be used in .mk file because
+ * '%' is a special char and cannot be easily escaped in Makefile.
+ *
+ * This is a workround for test purpose.
+ * Only package name is used because activity name varies on
+ * different testing framework.
+ * Hopefully, the double "@@" and "^^" are not used in other cases.
+ */
+ // {"com.foo.bar", ".A%"} -> "com.foo.bar"
+ std::string ToMakeFileSafeEncodedPkgString() const {
+ std::string s = package;
+ Replace(s, "/", "@@");
+ Replace(s, "%", "^^");
+ return s;
+ }
+
+ private:
+ static bool Replace(std::string& str, const std::string& from, const std::string& to) {
+ // TODO: call in a loop to replace all occurrences, not just the first one.
+ const size_t start_pos = str.find(from);
+ if (start_pos == std::string::npos) {
+ return false;
+ }
+
+ str.replace(start_pos, from.length(), to);
+
+ return true;
+}
+};
+
+inline std::ostream& operator<<(std::ostream& os, const AppComponentName& name) {
+ os << name.ToString();
+ return os;
+}
+
+} // namespace iorap::db
+
+#endif // IORAP_SRC_DB_APP_COMPONENT_NAME_H_
diff --git a/src/db/clean_up.cc b/src/db/clean_up.cc
new file mode 100644
index 0000000..de1412d
--- /dev/null
+++ b/src/db/clean_up.cc
@@ -0,0 +1,115 @@
+// 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 "db/clean_up.h"
+
+#include <android-base/file.h>
+
+#include <cstdio>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "db/file_models.h"
+#include "db/models.h"
+
+namespace iorap::db {
+
+void CleanUpFilesForActivity(const db::DbHandle& db,
+ const db::VersionedComponentName& vcn) {
+ LOG(DEBUG) << "Clean up files for activity " << vcn.GetActivity();
+ // Remove perfetto traces.
+ std::vector<db::RawTraceModel> raw_traces =
+ db::RawTraceModel::SelectByVersionedComponentName(db, vcn);
+ for (db::RawTraceModel raw_trace : raw_traces) {
+ std::string file_path = raw_trace.file_path;
+ LOG(DEBUG) << "Remove file: " << file_path;
+ std::filesystem::remove(file_path.c_str());
+ raw_trace.Delete();
+ }
+
+ // Remove compiled traces.
+ std::optional<db::PrefetchFileModel> prefetch_file =
+ db::PrefetchFileModel::SelectByVersionedComponentName(db, vcn);
+
+ if (prefetch_file) {
+ std::string file_path = prefetch_file->file_path;
+ LOG(DEBUG) << "Remove file: " << file_path;
+ std::filesystem::remove(file_path.c_str());
+ prefetch_file->Delete();
+ }
+}
+
+void CleanUpFilesForPackage(const db::DbHandle& db,
+ int package_id,
+ const std::string& package_name,
+ int64_t version) {
+ LOG(DEBUG) << "Clean up files for package " << package_name << " with version "
+ << version;
+ std::vector<db::ActivityModel> activities =
+ db::ActivityModel::SelectByPackageId(db, package_id);
+
+ for (db::ActivityModel activity : activities) {
+ db::VersionedComponentName vcn{package_name, activity.name, version};
+ CleanUpFilesForActivity(db, vcn);
+ }
+}
+
+void CleanUpFilesForPackage(const db::DbHandle& db,
+ const std::string& package_name,
+ int64_t version) {
+ std::optional<PackageModel> package =
+ PackageModel::SelectByNameAndVersion(db, package_name.c_str(), version);
+
+ if (!package) {
+ LOG(DEBUG) << "No package to clean up " << package_name << " with version " << version;
+ return;
+ }
+
+ CleanUpFilesForPackage(db, package->id, package_name, version);
+}
+
+void CleanUpFilesForDb(const db::DbHandle& db) {
+ std::vector<db::PackageModel> packages = db::PackageModel::SelectAll(db);
+ for (db::PackageModel package : packages) {
+ CleanUpFilesForPackage(db, package.id, package.name, package.version);
+ }
+}
+
+void CleanUpFilesForPackage(const std::string& db_path,
+ const std::string& package_name) {
+ iorap::db::SchemaModel db_schema = db::SchemaModel::GetOrCreate(db_path);
+ db::DbHandle db{db_schema.db()};
+ CleanUpFilesForPackage(db, package_name);
+}
+
+
+void CleanUpFilesForPackage(const db::DbHandle& db,
+ const std::string& package_name) {
+ std::vector<PackageModel> packages = PackageModel::SelectByName(db, package_name.c_str());;
+
+ for (PackageModel& package : packages) {
+ CleanUpFilesForPackage(db, package.id, package.name, package.version);
+ }
+
+ if (packages.empty()) {
+ LOG(DEBUG) << "No package rows to clean up " << package_name;
+ }
+}
+
+} // namespace iorap::db
diff --git a/src/db/clean_up.h b/src/db/clean_up.h
new file mode 100644
index 0000000..08b7bde
--- /dev/null
+++ b/src/db/clean_up.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef IORAP_SRC_DB_CLEANER_H_
+#define IORAP_SRC_DB_CLEANER_H_
+
+#include <android/content/pm/IPackageManagerNative.h>
+
+#include <string>
+#include <vector>
+
+#include "binder/package_version_map.h"
+#include "db/file_models.h"
+
+namespace iorap::db {
+
+// Clean up perfetto traces and compiled traces in disk and rows
+// in raw_traces and prefetch_files in the db.
+void CleanUpFilesForDb(const db::DbHandle& db);
+
+// Clean up perfetto traces and compiled traces in disk and rows
+// in raw_traces and prefetch_files in the db for a package id.
+void CleanUpFilesForPackage(const db::DbHandle& db,
+ int package_id,
+ const std::string& package_name,
+ int64_t version);
+
+// Clean up all package rows (and files) associated with a package by name.
+void CleanUpFilesForPackage(const std::string& db_path,
+ const std::string& package_name);
+
+// Clean up all package rows (and files) associated with a package by name.
+void CleanUpFilesForPackage(const db::DbHandle& db,
+ const std::string& package_name);
+// Clean up perfetto traces and compiled traces in disk and rows
+// in raw_traces and prefetch_files in the db for a package name
+// and version.
+void CleanUpFilesForPackage(const db::DbHandle& db,
+ const std::string& package_name,
+ int64_t version);
+} // namespace iorap::db
+
+#endif // IORAP_SRC_DB_CLEANER_H_
diff --git a/src/db/file_models.cc b/src/db/file_models.cc
new file mode 100644
index 0000000..0fe6d8f
--- /dev/null
+++ b/src/db/file_models.cc
@@ -0,0 +1,181 @@
+// Copyright (C) 2019 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 "common/cmd_utils.h"
+#include "db/file_models.h"
+#include "db/models.h"
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+
+#include <algorithm>
+#include <sstream>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+namespace iorap::db {
+
+static constexpr const char* kRootPathProp = "iorapd.root.dir";
+static const unsigned int kPerfettoMaxTraces =
+ ::android::base::GetUintProperty("iorapd.perfetto.max_traces", /*default*/10u);
+
+static uint64_t GetTimeNanoseconds() {
+ struct timespec now;
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ uint64_t now_ns = (now.tv_sec * 1000000000LL + now.tv_nsec);
+ return now_ns;
+}
+
+static bool IsDir(const std::string& dirpath) {
+ struct stat st;
+ if (stat(dirpath.c_str(), &st) == 0) {
+ if (S_ISDIR(st.st_mode)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Given some path /a/b/c create all of /a, /a/b, /a/b/c/ recursively.
+static bool MkdirWithParents(const std::string& path) {
+ size_t prev_end = 0;
+ while (prev_end < path.size()) {
+ size_t next_end = path.find('/', prev_end + 1);
+
+ std::string dir_path = path.substr(0, next_end);
+ if (!IsDir(dir_path)) {
+#if defined(_WIN32)
+ int ret = mkdir(dir_path.c_str());
+#else
+ mode_t old_mask = umask(0);
+ // The user permission is 5 to allow system server to
+ // read the files. No other users could do that because
+ // the upper directory only allows system server and iorapd
+ // to access. Also selinux rules prevent other users to
+ // read files here.
+ int ret = mkdir(dir_path.c_str(), 0755);
+ umask(old_mask);
+#endif
+ if (ret != 0) {
+ PLOG(ERROR) << "failed to create dir " << dir_path;
+ return false;
+ }
+ }
+ prev_end = next_end;
+
+ if (next_end == std::string::npos) {
+ break;
+ }
+ }
+ return true;
+}
+
+FileModelBase::FileModelBase(VersionedComponentName vcn)
+ : vcn_{std::move(vcn)} {
+ root_path_ = common::GetEnvOrProperty(kRootPathProp,
+ /*default*/"/data/misc/iorapd");
+}
+
+std::string FileModelBase::BaseDir() const {
+ std::stringstream ss;
+
+ ss << root_path_ << "/" << vcn_.GetPackage() << "/";
+ ss << vcn_.GetVersion();
+ ss << "/";
+ ss << vcn_.GetActivity() << "/";
+ ss << SubDir();
+
+ return ss.str();
+}
+
+std::string FileModelBase::FilePath() const {
+ std::stringstream ss;
+ ss << BaseDir();
+ ss << "/";
+ ss << BaseFile();
+
+ return ss.str();
+}
+
+bool FileModelBase::MkdirWithParents() {
+ LOG(VERBOSE) << "MkdirWithParents: " << BaseDir();
+ return ::iorap::db::MkdirWithParents(BaseDir());
+}
+
+PerfettoTraceFileModel::PerfettoTraceFileModel(VersionedComponentName vcn,
+ uint64_t timestamp)
+ : FileModelBase{std::move(vcn)}, timestamp_{timestamp} {
+}
+
+PerfettoTraceFileModel PerfettoTraceFileModel::CalculateNewestFilePath(VersionedComponentName vcn) {
+ uint64_t timestamp = GetTimeNanoseconds();
+ return PerfettoTraceFileModel{vcn, timestamp};
+}
+
+std::string PerfettoTraceFileModel::BaseFile() const {
+ std::stringstream ss;
+ ss << timestamp_ << ".perfetto_trace.pb";
+ return ss.str();
+}
+
+void PerfettoTraceFileModel::DeleteOlderFiles(DbHandle& db, VersionedComponentName vcn) {
+ std::vector<RawTraceModel> raw_traces =
+ RawTraceModel::SelectByVersionedComponentName(db, vcn);
+
+ if (WOULD_LOG(VERBOSE)) {
+ size_t raw_traces_size = raw_traces.size();
+ for (size_t i = 0; i < raw_traces_size; ++i) {
+ LOG(VERBOSE) << "DeleteOlderFiles - selected " << raw_traces[i];
+ }
+ LOG(VERBOSE) << "DeleteOlderFiles - queried total " << raw_traces_size << " records";
+ }
+
+ size_t items_to_delete = 0;
+ if (raw_traces.size() > kPerfettoMaxTraces) {
+ items_to_delete = raw_traces.size() - kPerfettoMaxTraces;
+ } else {
+ LOG(VERBOSE) << "DeleteOlderFiles - don't delete older raw traces, too few files: "
+ << " wanted at least " << kPerfettoMaxTraces << ", but got " << raw_traces.size();
+ }
+
+ for (size_t i = 0; i < items_to_delete; ++i) {
+ RawTraceModel& raw_trace = raw_traces[i]; // sorted ascending -> items to delete are first.
+ std::string err_msg;
+
+ if (!::android::base::RemoveFileIfExists(raw_trace.file_path, /*out*/&err_msg)) {
+ LOG(ERROR) << "Failed to remove raw trace file: " << raw_trace.file_path
+ << ", reason: " << err_msg;
+ } else {
+ raw_trace.Delete();
+ LOG(DEBUG) << "Deleted raw trace for " << vcn << " at " << raw_trace.file_path;
+ }
+ }
+}
+
+CompiledTraceFileModel::CompiledTraceFileModel(VersionedComponentName vcn)
+ : FileModelBase{std::move(vcn)} {
+}
+
+CompiledTraceFileModel CompiledTraceFileModel::CalculateNewestFilePath(VersionedComponentName vcn) {
+ return CompiledTraceFileModel{vcn};
+}
+
+std::string CompiledTraceFileModel::BaseFile() const {
+ return "compiled_trace.pb";
+}
+
+} // namespace iorap::db
diff --git a/src/db/file_models.h b/src/db/file_models.h
new file mode 100644
index 0000000..135c98a
--- /dev/null
+++ b/src/db/file_models.h
@@ -0,0 +1,124 @@
+// Copyright (C) 2019 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 IORAP_SRC_DB_FILE_MODELS_H_
+#define IORAP_SRC_DB_FILE_MODELS_H_
+
+#include <optional>
+#include <ostream>
+#include <string>
+
+namespace iorap::db {
+
+struct VersionedComponentName {
+ VersionedComponentName(std::string package,
+ std::string activity,
+ int64_t version)
+ : package_{std::move(package)},
+ activity_{std::move(activity)},
+ version_{version} {
+ }
+
+ const std::string& GetPackage() const {
+ return package_;
+ }
+
+ const std::string& GetActivity() const {
+ return activity_;
+ }
+
+ int GetVersion() const {
+ return version_;
+ }
+
+ private:
+ std::string package_;
+ std::string activity_;
+ int64_t version_;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const VersionedComponentName& vcn) {
+ os << vcn.GetPackage() << "/" << vcn.GetActivity() << "@" << vcn.GetVersion();
+ return os;
+}
+
+class FileModelBase {
+ protected:
+ FileModelBase(VersionedComponentName vcn);
+
+ virtual std::string SubDir() const = 0;
+ // Include the last file component only (/a/b/c.txt -> c.txt)
+ virtual std::string BaseFile() const = 0;
+
+ virtual ~FileModelBase() {}
+
+ public:
+ virtual std::string ModelName() const = 0;
+ // Return the absolute file path to this FileModel.
+ std::string FilePath() const;
+
+ // Include the full path minus the basefile (/a/b/c.txt -> /a/b)
+ std::string BaseDir() const;
+
+ // Create the base dir recursively (e.g. mkdir -p $basedir).
+ bool MkdirWithParents();
+ private:
+ VersionedComponentName vcn_;
+ std::string subdir_;
+ std::string filename_;
+ std::string root_path_;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const FileModelBase& fmb) {
+ os << fmb.ModelName() << "{'" << fmb.FilePath() << "'}";
+ return os;
+}
+
+class DbHandle;
+
+class PerfettoTraceFileModel : public FileModelBase {
+ protected:
+ virtual std::string ModelName() const override { return "PerfettoTrace"; }
+ virtual std::string SubDir() const override { return "raw_traces"; }
+ virtual std::string BaseFile() const override;
+
+ PerfettoTraceFileModel(VersionedComponentName vcn,
+ uint64_t timestamp);
+
+ public:
+ static PerfettoTraceFileModel CalculateNewestFilePath(VersionedComponentName vcn);
+ static void DeleteOlderFiles(DbHandle& db, VersionedComponentName vcn);
+
+ virtual ~PerfettoTraceFileModel() {}
+ private:
+ uint64_t timestamp_;
+};
+
+class CompiledTraceFileModel : public FileModelBase {
+ protected:
+ virtual std::string ModelName() const override { return "CompiledTrace"; }
+ virtual std::string SubDir() const override { return "compiled_traces"; }
+ virtual std::string BaseFile() const override;
+
+ CompiledTraceFileModel(VersionedComponentName vcn);
+
+ public:
+ static CompiledTraceFileModel CalculateNewestFilePath(VersionedComponentName vcn);
+
+ virtual ~CompiledTraceFileModel() {}
+};
+
+} // namespace iorap::db
+
+#endif // IORAP_SRC_DB_FILE_MODELS_H_
diff --git a/src/db/main.cc b/src/db/main.cc
new file mode 100644
index 0000000..2edd961
--- /dev/null
+++ b/src/db/main.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2019 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 "common/debug.h"
+#include "common/loggers.h"
+#include "db/app_component_name.h"
+#include "db/models.h"
+
+#include <android-base/parseint.h>
+#include <android-base/logging.h>
+
+#include <iostream>
+#include <optional>
+#include <string_view>
+#include <string>
+#include <vector>
+
+#include <sqlite3.h>
+
+#include <signal.h>
+
+namespace iorap::db {
+
+void Usage(char** argv) {
+ std::cerr << "Usage: " << argv[0] << " <path-to-sqlite.db>" << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Interface with the iorap sqlite database and issue commands." << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Optional flags:" << std::endl;
+ std::cerr << " --help,-h Print this Usage." << std::endl;
+ std::cerr << " --register-raw-trace,-rrt Register raw trace file path." << std::endl;
+ std::cerr << " --register-compiled-trace,-rct Register compiled trace file path." << std::endl;
+ std::cerr << " --insert-component,-ic Add component if it doesn't exist." << std::endl;
+ std::cerr << " --initialize,-i Initialize new database." << std::endl;
+ std::cerr << " --rescan,-rs Update all from canonical directories." << std::endl;
+ std::cerr << " --prune,-pr Remove any stale file paths." << std::endl;
+ std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
+ std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
+ exit(1);
+}
+
+void error_log_sqlite_callback(void *pArg, int iErrCode, const char *zMsg) {
+ LOG(ERROR) << "SQLite error (" << iErrCode << "): " << zMsg;
+}
+
+const constexpr int64_t kNoVersion = -1;
+
+int Main(int argc, char** argv) {
+ // Go to system logcat + stderr when running from command line.
+ android::base::InitLogging(argv, iorap::common::StderrAndLogdLogger{android::base::SYSTEM});
+
+ bool wait_for_keystroke = false;
+ bool enable_verbose = false;
+
+ bool command_format_text = false; // false = binary.
+
+ int arg_input_fd = -1;
+ int arg_output_fd = -1;
+
+ std::vector<std::string> arg_input_filenames;
+ bool arg_use_sockets = false;
+
+ std::vector<std::pair<std::string,std::string>> arg_register_raw_trace;
+ std::vector<std::pair<std::string,std::string>> arg_register_compiled_trace;
+
+ std::vector<std::string> arg_insert_component;
+
+ bool arg_initialize = false;
+ bool arg_rescan = false;
+ bool arg_prune = false;
+
+ LOG(VERBOSE) << "argparse: argc=" << argc;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+ bool has_arg_next = (arg+1)<argc;
+ std::string arg_next = has_arg_next ? argv[arg+1] : "";
+
+ bool has_arg_next_next = (arg+2)<argc;
+ std::string arg_next_next = has_arg_next_next ? argv[arg+2] : "";
+
+ LOG(VERBOSE) << "argparse: argv[" << arg << "]=" << argstr;
+
+ if (argstr == "--help" || argstr == "-h") {
+ Usage(argv);
+ } else if (argstr == "--register-raw-trace" || argstr == "-rrt") {
+ if (!has_arg_next_next) {
+ LOG(ERROR) << "--register-raw-trace <component/name> <filepath>";
+ Usage(argv);
+ }
+ arg_register_raw_trace.push_back({arg_next, arg_next_next});
+ } else if (argstr == "--register-compiled-trace" || argstr == "-rct") {
+ if (!has_arg_next_next) {
+ LOG(ERROR) << "--register-compiled-trace <component/name> <filepath>";
+ Usage(argv);
+ }
+ arg_register_compiled_trace.push_back({arg_next, arg_next_next});
+ } else if (argstr == "--insert-component" || argstr == "-ic") {
+ if (!has_arg_next) {
+ LOG(ERROR) << "--insert-component <component/name>";
+ Usage(argv);
+ }
+ arg_insert_component.push_back(arg_next);
+ } else if (argstr == "--initialize" || argstr == "-i") {
+ arg_initialize = true;
+ } else if (argstr == "--rescan" || argstr == "-rs") {
+ arg_rescan = true;
+ } else if (argstr == "--prune" || argstr == "-pr") {
+ arg_prune = true;
+ } else if (argstr == "--verbose" || argstr == "-v") {
+ enable_verbose = true;
+ } else if (argstr == "--wait" || argstr == "-w") {
+ wait_for_keystroke = true;
+ } else {
+ arg_input_filenames.push_back(argstr);
+ }
+ }
+
+ if (arg_input_filenames.empty()) {
+ LOG(ERROR) << "Missing positional filename to a sqlite database.";
+ Usage(argv);
+ }
+
+ if (enable_verbose) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+ LOG(VERBOSE) << "Verbose check";
+ LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::DEBUG);
+ }
+
+ LOG(VERBOSE) << "argparse: argc=" << argc;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+
+ LOG(VERBOSE) << "argparse: argv[" << arg << "]=" << argstr;
+ }
+
+ // Useful to attach a debugger...
+ // 1) $> iorap.cmd.readahead -w <args>
+ // 2) $> gdbclient <pid>
+ if (wait_for_keystroke) {
+ LOG(INFO) << "Self pid: " << getpid();
+
+ raise(SIGSTOP);
+ // LOG(INFO) << "Press any key to continue...";
+ // std::cin >> wait_for_keystroke;
+ }
+
+ // auto system_call = std::make_unique<SystemCallImpl>();
+ // TODO: mock readahead calls?
+ //
+ // Uncomment this if we want to leave the process around to inspect it from adb shell.
+ // sleep(100000);
+
+ int return_code = 0;
+
+ LOG(VERBOSE) << "Hello world";
+
+
+ do {
+ SchemaModel schema_model = SchemaModel::GetOrCreate(arg_input_filenames[0]);
+ DbHandle db = schema_model.db();
+ if (arg_initialize) {
+ // Drop tables and restart from scratch. All rows are effectively dropped.
+ schema_model.Reinitialize();
+ }
+
+ for (const auto& component_and_file_name : arg_register_raw_trace) {
+ AppComponentName component_name = AppComponentName::FromString(component_and_file_name.first);
+ const std::string& file_path = component_and_file_name.second;
+
+ LOG(VERBOSE) << "--register-raw-trace " << component_name << ", file_path: " << file_path;
+
+ std::optional<ActivityModel> activity =
+ ActivityModel::SelectOrInsert(db,
+ component_name.package,
+ kNoVersion,
+ component_name.activity_name);
+ DCHECK(activity.has_value());
+ LOG(DEBUG) << "Component selected/inserted: " << *activity;
+ }
+
+ for (const std::string& component : arg_insert_component) {
+ AppComponentName component_name = AppComponentName::FromString(component);
+
+ LOG(VERBOSE) << "raw component: " << component;
+ LOG(VERBOSE) << "package: " << component_name.package;
+ LOG(VERBOSE) << "activity name: " << component_name.activity_name;
+
+ LOG(VERBOSE) << "--insert-component " << component_name;
+
+ std::optional<ActivityModel> activity =
+ ActivityModel::SelectOrInsert(db,
+ component_name.package,
+ kNoVersion,
+ component_name.activity_name);
+
+ DCHECK(activity.has_value());
+ LOG(DEBUG) << "Component selected/inserted: " << *activity;
+ }
+ } while (false);
+
+ LOG(VERBOSE) << "main: Terminating";
+
+ // 0 -> successfully executed all commands.
+ // 1 -> failed along the way (#on_error and also see the error logs).
+ return return_code;
+}
+
+} // namespace iorap::db
+
+#if defined(IORAP_DB_MAIN)
+int main(int argc, char** argv) {
+ return ::iorap::db::Main(argc, argv);
+}
+#endif // IORAP_DB_MAIN
diff --git a/src/db/models.cc b/src/db/models.cc
new file mode 100644
index 0000000..851a061
--- /dev/null
+++ b/src/db/models.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 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 "db/models.h"
+
+namespace iorap::db {
+
+std::optional<DbHandle> SchemaModel::s_singleton_;
+
+} // namespace iorap::db
diff --git a/src/db/models.h b/src/db/models.h
new file mode 100644
index 0000000..df6beb6
--- /dev/null
+++ b/src/db/models.h
@@ -0,0 +1,1130 @@
+// Copyright (C) 2019 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 IORAP_SRC_DB_MODELS_H_
+#define IORAP_SRC_DB_MODELS_H_
+
+#include "clean_up.h"
+#include "file_models.h"
+
+#include <android-base/logging.h>
+#include <utils/String8.h>
+
+#include <filesystem>
+#include <iostream>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <sstream>
+#include <type_traits>
+#include <vector>
+
+#include <sqlite3.h>
+
+namespace iorap::db {
+
+const constexpr int kDbVersion = 2;
+
+struct SqliteDbDeleter {
+ void operator()(sqlite3* db) {
+ if (db != nullptr) {
+ LOG(VERBOSE) << "sqlite3_close for: " << db;
+ sqlite3_close(db);
+ }
+ }
+};
+
+class DbHandle {
+ public:
+ // Take over ownership of sqlite3 db.
+ explicit DbHandle(sqlite3* db)
+ : db_{std::shared_ptr<sqlite3>{db, SqliteDbDeleter{}}},
+ mutex_{std::make_shared<std::mutex>()} {
+ }
+
+ sqlite3* get() {
+ return db_.get();
+ }
+
+ operator sqlite3*() {
+ return db_.get();
+ }
+
+ std::mutex& mutex() {
+ return *mutex_.get();
+ }
+
+ private:
+ std::shared_ptr<sqlite3> db_;
+ std::shared_ptr<std::mutex> mutex_;
+};
+
+class ScopedLockDb {
+ public:
+ ScopedLockDb(std::mutex& mutex) : mutex(mutex) {
+ mutex.lock();
+ }
+
+ ScopedLockDb(DbHandle& handle) : ScopedLockDb(handle.mutex()) {
+ }
+
+ ~ScopedLockDb() {
+ mutex.unlock();
+ }
+ private:
+ std::mutex& mutex;
+};
+
+class DbStatement {
+ public:
+ template <typename ... Args>
+ static DbStatement Prepare(DbHandle db, const std::string& sql, Args&&... args) {
+ return Prepare(db, sql.c_str(), std::forward<Args>(args)...);
+ }
+
+ template <typename ... Args>
+ static DbStatement Prepare(DbHandle db, const char* sql, Args&&... args) {
+ DCHECK(db.get() != nullptr);
+ DCHECK(sql != nullptr);
+
+ // LOG(VERBOSE) << "Prepare DB=" << db.get();
+
+ sqlite3_stmt* stmt = nullptr;
+ int rc = sqlite3_prepare_v2(db.get(), sql, -1, /*out*/&stmt, nullptr);
+
+ DbStatement db_stmt{db, stmt};
+ DCHECK(db_stmt.CheckOk(rc)) << sql;
+ db_stmt.BindAll(std::forward<Args>(args)...);
+
+ return db_stmt;
+ }
+
+ DbStatement(DbHandle db, sqlite3_stmt* stmt) : db_(db), stmt_(stmt) {
+ }
+
+ sqlite3_stmt* get() {
+ return stmt_;
+ }
+
+ DbHandle db() {
+ return db_;
+ }
+
+ // Successive BindAll calls *do not* start back at the 0th bind position.
+ template <typename T, typename ... Args>
+ void BindAll(T&& arg, Args&&... args) {
+ Bind(std::forward<T>(arg));
+ BindAll(std::forward<Args>(args)...);
+ }
+
+ void BindAll() {}
+
+ template <typename T>
+ void Bind(const std::optional<T>& value) {
+ if (value) {
+ Bind(*value);
+ } else {
+ BindNull();
+ }
+ }
+
+ void Bind(bool value) {
+ CheckOk(sqlite3_bind_int(stmt_, bind_counter_++, value));
+ }
+
+ void Bind(int value) {
+ CheckOk(sqlite3_bind_int(stmt_, bind_counter_++, value));
+ }
+
+ void Bind(uint64_t value) {
+ CheckOk(sqlite3_bind_int64(stmt_, bind_counter_++, static_cast<int64_t>(value)));
+ }
+
+ void Bind(const char* value) {
+ if (value != nullptr) {
+ //sqlite3_bind_text(stmt_, /*index*/bind_counter_++, value, -1, SQLITE_STATIC);
+ CheckOk(sqlite3_bind_text(stmt_, /*index*/bind_counter_++, value, -1, SQLITE_TRANSIENT));
+ } else {
+ BindNull();
+ }
+ }
+
+ void Bind(const std::string& value) {
+ Bind(value.c_str());
+ }
+
+ template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
+ void Bind(E value) {
+ Bind(static_cast<std::underlying_type_t<E>>(value));
+ }
+
+ void BindNull() {
+ CheckOk(sqlite3_bind_null(stmt_, bind_counter_++));
+ }
+
+ int Step() {
+ ++step_counter_;
+ return sqlite3_step(stmt_);
+ }
+
+ bool Step(int expected) {
+ int rc = Step();
+ if (rc != expected) {
+ LOG(ERROR) << "SQLite error: " << sqlite3_errmsg(db_.get());
+ return false;
+ }
+ return true;
+ }
+
+ // Successive ColumnAll calls start at the 0th column again.
+ template <typename T, typename ... Args>
+ void ColumnAll(T& first, Args&... rest) {
+ Column(first);
+ ColumnAll(rest...);
+ // Reset column counter back to 0
+ column_counter_ = 0;
+ }
+
+ void ColumnAll() {}
+
+ template <typename T>
+ void Column(std::optional<T>& value) {
+ T tmp;
+ Column(/*out*/tmp);
+
+ if (!tmp) { // disambiguate 0 and NULL
+ const unsigned char* text = sqlite3_column_text(stmt_, column_counter_ - 1);
+ if (text == nullptr) {
+ value = std::nullopt;
+ return;
+ }
+ }
+ value = std::move(tmp);
+ }
+
+ template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
+ void Column(E& value) {
+ std::underlying_type_t<E> tmp;
+ Column(/*out*/tmp);
+ value = static_cast<E>(tmp);
+ }
+
+ void Column(bool& value) {
+ value = sqlite3_column_int(stmt_, column_counter_++);
+ }
+
+ void Column(int& value) {
+ value = sqlite3_column_int(stmt_, column_counter_++);
+ }
+
+ void Column(uint64_t& value) {
+ value = static_cast<uint64_t>(sqlite3_column_int64(stmt_, column_counter_++));
+ }
+
+ void Column(std::string& value) {
+ const unsigned char* text = sqlite3_column_text(stmt_, column_counter_++);
+
+ DCHECK(text != nullptr) << "Column should be marked NOT NULL, otherwise use optional<string>";
+ if (text == nullptr) {
+ LOG(ERROR) << "Got NULL back for column " << column_counter_-1
+ << "; is this column marked NOT NULL?";
+ value = "(((null)))"; // Don't segfault, keep going.
+ return;
+ }
+
+ value = std::string{reinterpret_cast<const char*>(text)};
+ }
+
+ const char* ExpandedSql() {
+ char* p = sqlite3_expanded_sql(stmt_);
+ if (p == nullptr) {
+ return "(nullptr)";
+ }
+ return p;
+ }
+
+ const char* Sql() {
+ const char* p = sqlite3_sql(stmt_);
+ if (p == nullptr) {
+ return "(nullptr)";
+ }
+ return p;
+ }
+
+
+ DbStatement(DbStatement&& other)
+ : db_{other.db_}, stmt_{other.stmt_}, bind_counter_{other.bind_counter_},
+ step_counter_{other.step_counter_} {
+ other.db_ = DbHandle{nullptr};
+ other.stmt_ = nullptr;
+ }
+
+ ~DbStatement() {
+ if (stmt_ != nullptr) {
+ DCHECK_GT(step_counter_, 0) << " forgot to call Step()?";
+ sqlite3_finalize(stmt_);
+ }
+ }
+
+ private:
+ bool CheckOk(int rc, int expected = SQLITE_OK) {
+ if (rc != expected) {
+ LOG(ERROR) << "Got error for SQL query: '" << Sql() << "'"
+ << ", expanded: '" << ExpandedSql() << "'";
+ LOG(ERROR) << "Failed SQLite api call (" << rc << "): " << sqlite3_errstr(rc);
+ }
+ return rc == expected;
+ }
+
+ DbHandle db_;
+ sqlite3_stmt* stmt_;
+ int bind_counter_ = 1;
+ int step_counter_ = 0;
+ int column_counter_ = 0;
+};
+
+class DbQueryBuilder {
+ public:
+ // Returns the row ID that was inserted last.
+ template <typename... Args>
+ static std::optional<int> Insert(DbHandle db, const std::string& sql, Args&&... args) {
+ ScopedLockDb lock{db};
+
+ sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(db.get());
+ DbStatement stmt = DbStatement::Prepare(db, sql, std::forward<Args>(args)...);
+
+ if (!stmt.Step(SQLITE_DONE)) {
+ return std::nullopt;
+ }
+
+ last_rowid = sqlite3_last_insert_rowid(db.get());
+ DCHECK_GT(last_rowid, 0);
+
+ return static_cast<int>(last_rowid);
+ }
+
+ template <typename... Args>
+ static bool Delete(DbHandle db, const std::string& sql, Args&&... args) {
+ return ExecuteOnce(db, sql, std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ static bool Update(DbHandle db, const std::string& sql, Args&&... args) {
+ return ExecuteOnce(db, sql, std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ static bool ExecuteOnce(DbHandle db, const std::string& sql, Args&&... args) {
+ ScopedLockDb lock{db};
+
+ DbStatement stmt = DbStatement::Prepare(db, sql, std::forward<Args>(args)...);
+
+ if (!stmt.Step(SQLITE_DONE)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ template <typename... Args>
+ static bool SelectOnce(DbStatement& stmt, Args&... args) {
+ int rc = stmt.Step();
+ switch (rc) {
+ case SQLITE_ROW:
+ stmt.ColumnAll(/*out*/args...);
+ return true;
+ case SQLITE_DONE:
+ return false;
+ default:
+ LOG(ERROR) << "Failed to step (" << rc << "): " << sqlite3_errmsg(stmt.db());
+ return false;
+ }
+ }
+};
+
+class Model {
+ public:
+ DbHandle db() const {
+ return db_;
+ }
+
+ Model(DbHandle db) : db_{db} {
+ }
+
+ private:
+ DbHandle db_;
+};
+
+class SchemaModel : public Model {
+ public:
+ static SchemaModel GetOrCreate(std::string location) {
+ int rc = sqlite3_config(SQLITE_CONFIG_LOG, ErrorLogCallback, /*data*/nullptr);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to configure logging";
+ }
+
+ sqlite3* db = nullptr;
+ bool is_deprecated = false;
+ if (location != ":memory:") {
+ // Try to open DB if it already exists.
+ rc = sqlite3_open_v2(location.c_str(), /*out*/&db, SQLITE_OPEN_READWRITE, /*vfs*/nullptr);
+
+ if (rc == SQLITE_OK) {
+ LOG(INFO) << "Opened existing database at '" << location << "'";
+ SchemaModel schema{DbHandle{db}, location};
+ if (schema.Version() == kDbVersion) {
+ return schema;
+ } else {
+ LOG(DEBUG) << "The version is old, reinit the db."
+ << " old version is "
+ << schema.Version()
+ << " and new version is "
+ << kDbVersion;
+ CleanUpFilesForDb(schema.db());
+ is_deprecated = true;
+ }
+ }
+ }
+
+ if (is_deprecated) {
+ // Remove the db and recreate it.
+ // TODO: migrate to a newer version without deleting the old one.
+ std::filesystem::remove(location.c_str());
+ }
+
+ // Create a new DB if one didn't exist already.
+ rc = sqlite3_open(location.c_str(), /*out*/&db);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to open DB: " << sqlite3_errmsg(db);
+ }
+
+ SchemaModel schema{DbHandle{db}, location};
+ schema.Reinitialize();
+ // TODO: migrate versions upwards when we rev the schema version
+
+ int old_version = schema.Version();
+ LOG(VERBOSE) << "Loaded schema version: " << old_version;
+
+ return schema;
+ }
+
+ void MarkSingleton() {
+ s_singleton_ = db();
+ }
+
+ static DbHandle GetSingleton() {
+ DCHECK(s_singleton_.has_value());
+ return *s_singleton_;
+ }
+
+ void Reinitialize() {
+ const char* sql_to_initialize = R"SQLC0D3(
+ DROP TABLE IF EXISTS schema_versions;
+ DROP TABLE IF EXISTS packages;
+ DROP TABLE IF EXISTS activities;
+ DROP TABLE IF EXISTS app_launch_histories;
+ DROP TABLE IF EXISTS raw_traces;
+ DROP TABLE IF EXISTS prefetch_files;
+)SQLC0D3";
+ char* err_msg = nullptr;
+ int rc = sqlite3_exec(db().get(),
+ sql_to_initialize,
+ /*callback*/nullptr,
+ /*arg*/0,
+ /*out*/&err_msg);
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to drop tables: " << err_msg ? err_msg : "nullptr";
+ }
+
+ CreateSchema();
+ LOG(INFO) << "Reinitialized database at '" << location_ << "'";
+ }
+
+ int Version() {
+ std::string query = "SELECT MAX(version) FROM schema_versions;";
+ DbStatement stmt = DbStatement::Prepare(db(), query);
+
+ int return_value = 0;
+ if (!DbQueryBuilder::SelectOnce(stmt, /*out*/return_value)) {
+ LOG(ERROR) << "Failed to query schema version";
+ return -1;
+ }
+
+ return return_value;
+ }
+
+ protected:
+ SchemaModel(DbHandle db, std::string location) : Model{db}, location_(location) {
+ }
+
+ private:
+ static std::optional<DbHandle> s_singleton_;
+
+ void CreateSchema() {
+ const char* sql_to_initialize = R"SQLC0D3(
+ CREATE TABLE schema_versions(
+ version INTEGER NOT NULL
+ );
+
+ CREATE TABLE packages(
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ version INTEGER NOT NULL,
+
+ PRIMARY KEY(id)
+ );
+
+ CREATE TABLE activities(
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ package_id INTEGER NOT NULL,
+
+ PRIMARY KEY(id),
+ FOREIGN KEY (package_id) REFERENCES packages (id) ON DELETE CASCADE
+ );
+
+ CREATE TABLE app_launch_histories(
+ id INTEGER NOT NULL PRIMARY KEY,
+ activity_id INTEGER NOT NULL,
+ -- 1:Cold, 2:Warm, 3:Hot
+ temperature INTEGER CHECK (temperature IN (1, 2, 3)) NOT NULL,
+ trace_enabled INTEGER CHECK(trace_enabled in (TRUE, FALSE)) NOT NULL,
+ readahead_enabled INTEGER CHECK(trace_enabled in (TRUE, FALSE)) NOT NULL,
+ -- absolute timestamp since epoch
+ intent_started_ns INTEGER CHECK(intent_started_ns IS NULL or intent_started_ns >= 0),
+ -- absolute timestamp since epoch
+ total_time_ns INTEGER CHECK(total_time_ns IS NULL or total_time_ns >= 0),
+ -- absolute timestamp since epoch
+ report_fully_drawn_ns INTEGER CHECK(report_fully_drawn_ns IS NULL or report_fully_drawn_ns >= 0),
+
+ FOREIGN KEY (activity_id) REFERENCES activities (id) ON DELETE CASCADE
+ );
+
+ CREATE TABLE raw_traces(
+ id INTEGER NOT NULL PRIMARY KEY,
+ history_id INTEGER NOT NULL,
+ file_path TEXT NOT NULL,
+
+ FOREIGN KEY (history_id) REFERENCES app_launch_histories (id) ON DELETE CASCADE
+ );
+
+ CREATE TABLE prefetch_files(
+ id INTEGER NOT NULL PRIMARY KEY,
+ activity_id INTEGER NOT NULL,
+ file_path TEXT NOT NULL,
+
+ FOREIGN KEY (activity_id) REFERENCES activities (id) ON DELETE CASCADE
+ );
+)SQLC0D3";
+
+ char* err_msg = nullptr;
+ int rc = sqlite3_exec(db().get(),
+ sql_to_initialize,
+ /*callback*/nullptr,
+ /*arg*/0,
+ /*out*/&err_msg);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to create tables: " << err_msg ? err_msg : "nullptr";
+ }
+
+ const char* sql_to_insert_schema_version = R"SQLC0D3(
+ INSERT INTO schema_versions VALUES(%d)
+ )SQLC0D3";
+ rc = sqlite3_exec(db().get(),
+ android::String8::format(sql_to_insert_schema_version,
+ kDbVersion),
+ /*callback*/nullptr,
+ /*arg*/0,
+ /*out*/&err_msg);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to insert the schema version: "
+ << err_msg ? err_msg : "nullptr";
+ }
+ }
+
+ static void ErrorLogCallback(void *pArg, int iErrCode, const char *zMsg) {
+ LOG(ERROR) << "SQLite error (" << iErrCode << "): " << zMsg;
+ }
+
+ std::string location_;
+};
+
+class PackageModel : public Model {
+ protected:
+ PackageModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ static std::optional<PackageModel> SelectById(DbHandle db, int id) {
+ ScopedLockDb lock{db};
+ int original_id = id;
+
+ std::string query = "SELECT * FROM packages WHERE id = ?1 LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, id);
+
+ PackageModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.version)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::vector<PackageModel> SelectByName(DbHandle db, const char* name) {
+ ScopedLockDb lock{db};
+
+ std::string query = "SELECT * FROM packages WHERE name = ?1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, name);
+
+ std::vector<PackageModel> packages;
+
+ PackageModel p{db};
+ while (DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.version)) {
+ packages.push_back(p);
+ }
+
+ return packages;
+ }
+
+ static std::optional<PackageModel> SelectByNameAndVersion(DbHandle db,
+ const char* name,
+ int version) {
+ ScopedLockDb lock{db};
+
+ std::string query =
+ "SELECT * FROM packages WHERE name = ?1 AND version = ?2 LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, name, version);
+
+ PackageModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.version)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::vector<PackageModel> SelectAll(DbHandle db) {
+ ScopedLockDb lock{db};
+
+ std::string query = "SELECT * FROM packages;";
+ DbStatement stmt = DbStatement::Prepare(db, query);
+
+ std::vector<PackageModel> packages;
+ PackageModel p{db};
+ while (DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.version)) {
+ packages.push_back(p);
+ }
+
+ return packages;
+ }
+
+ static std::optional<PackageModel> Insert(DbHandle db,
+ std::string name,
+ int version) {
+ const char* sql = "INSERT INTO packages (name, version) VALUES (?1, ?2);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db, sql, name, version);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ PackageModel p{db};
+ p.name = name;
+ p.version = version;
+ p.id = *inserted_row_id;
+
+ return p;
+ }
+
+ bool Delete() {
+ const char* sql = "DELETE FROM packages WHERE id = ?";
+
+ return DbQueryBuilder::Delete(db(), sql, id);
+ }
+
+ int id;
+ std::string name;
+ int version;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const PackageModel& p) {
+ os << "PackageModel{id=" << p.id << ",name=" << p.name << ",";
+ os << "version=";
+ os << p.version;
+ os << "}";
+ return os;
+}
+
+class ActivityModel : public Model {
+ protected:
+ ActivityModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ static std::optional<ActivityModel> SelectById(DbHandle db, int id) {
+ ScopedLockDb lock{db};
+ int original_id = id;
+
+ std::string query = "SELECT * FROM activities WHERE id = ? LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, id);
+
+ ActivityModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.package_id)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<ActivityModel> SelectByNameAndPackageId(DbHandle db,
+ const char* name,
+ int package_id) {
+ ScopedLockDb lock{db};
+
+ std::string query = "SELECT * FROM activities WHERE name = ? AND package_id = ? LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, name, package_id);
+
+ ActivityModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.package_id)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::vector<ActivityModel> SelectByPackageId(DbHandle db,
+ int package_id) {
+ ScopedLockDb lock{db};
+
+ std::string query = "SELECT * FROM activities WHERE package_id = ?;";
+ DbStatement stmt = DbStatement::Prepare(db, query, package_id);
+
+ std::vector<ActivityModel> activities;
+ ActivityModel p{db};
+ while (DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.package_id)) {
+ activities.push_back(p);
+ }
+
+ return activities;
+ }
+
+ static std::optional<ActivityModel> Insert(DbHandle db,
+ std::string name,
+ int package_id) {
+ const char* sql = "INSERT INTO activities (name, package_id) VALUES (?1, ?2);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db, sql, name, package_id);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ ActivityModel p{db};
+ p.id = *inserted_row_id;
+ p.name = name;
+ p.package_id = package_id;
+
+ return p;
+ }
+
+ // Try to select by package_name+activity_name, otherwise insert into both tables.
+ // Package version is ignored for selects.
+ static std::optional<ActivityModel> SelectOrInsert(
+ DbHandle db,
+ std::string package_name,
+ int package_version,
+ std::string activity_name) {
+ std::optional<PackageModel> package = PackageModel::SelectByNameAndVersion(db,
+ package_name.c_str(),
+ package_version);
+ if (!package) {
+ package = PackageModel::Insert(db, package_name, package_version);
+ DCHECK(package.has_value());
+ }
+
+ std::optional<ActivityModel> activity =
+ ActivityModel::SelectByNameAndPackageId(db,
+ activity_name.c_str(),
+ package->id);
+ if (!activity) {
+ activity = Insert(db, activity_name, package->id);
+ // XX: should we really return an optional here? This feels like it should never fail.
+ }
+
+ return activity;
+ }
+
+ int id;
+ std::string name;
+ int package_id; // PackageModel::id
+};
+
+inline std::ostream& operator<<(std::ostream& os, const ActivityModel& p) {
+ os << "ActivityModel{id=" << p.id << ",name=" << p.name << ",";
+ os << "package_id=" << p.package_id << "}";
+ return os;
+}
+
+class AppLaunchHistoryModel : public Model {
+ protected:
+ AppLaunchHistoryModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ enum class Temperature : int32_t {
+ kUninitialized = -1, // Note: Not a valid SQL value.
+ kCold = 1,
+ kWarm = 2,
+ kHot = 3,
+ };
+
+ static std::optional<AppLaunchHistoryModel> SelectById(DbHandle db, int id) {
+ ScopedLockDb lock{db};
+ int original_id = id;
+
+ std::string query = "SELECT * FROM app_launch_histories WHERE id = ? LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, id);
+
+ AppLaunchHistoryModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt,
+ p.id,
+ p.activity_id,
+ p.temperature,
+ p.trace_enabled,
+ p.readahead_enabled,
+ p.intent_started_ns,
+ p.total_time_ns,
+ p.report_fully_drawn_ns)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ // Selects the activity history for an activity id.
+ // The requirements are:
+ // * Should be cold run.
+ // * Pefetto trace is enabled.
+ // * intent_start_ns is *NOT* null.
+ static std::vector<AppLaunchHistoryModel> SelectActivityHistoryForCompile(
+ DbHandle db,
+ int activity_id) {
+ ScopedLockDb lock{db};
+ std::string query = "SELECT * FROM app_launch_histories "
+ "WHERE activity_id = ?1 AND"
+ " temperature = 1 AND"
+ " trace_enabled = TRUE AND"
+ " intent_started_ns IS NOT NULL;";
+ DbStatement stmt = DbStatement::Prepare(db, query, activity_id);
+ std::vector<AppLaunchHistoryModel> result;
+
+ AppLaunchHistoryModel p{db};
+ while (DbQueryBuilder::SelectOnce(stmt,
+ p.id,
+ p.activity_id,
+ p.temperature,
+ p.trace_enabled,
+ p.readahead_enabled,
+ p.intent_started_ns,
+ p.total_time_ns,
+ p.report_fully_drawn_ns)) {
+ result.push_back(p);
+ }
+ return result;
+ }
+
+ static std::optional<AppLaunchHistoryModel> Insert(DbHandle db,
+ int activity_id,
+ AppLaunchHistoryModel::Temperature temperature,
+ bool trace_enabled,
+ bool readahead_enabled,
+ std::optional<uint64_t> intent_started_ns,
+ std::optional<uint64_t> total_time_ns,
+ std::optional<uint64_t> report_fully_drawn_ns)
+ {
+ const char* sql = "INSERT INTO app_launch_histories (activity_id, temperature, trace_enabled, "
+ "readahead_enabled, intent_started_ns, "
+ "total_time_ns, report_fully_drawn_ns) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db,
+ sql,
+ activity_id,
+ temperature,
+ trace_enabled,
+ readahead_enabled,
+ intent_started_ns,
+ total_time_ns,
+ report_fully_drawn_ns);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ AppLaunchHistoryModel p{db};
+ p.id = *inserted_row_id;
+ p.activity_id = activity_id;
+ p.temperature = temperature;
+ p.trace_enabled = trace_enabled;
+ p.readahead_enabled = readahead_enabled;
+ p.intent_started_ns = intent_started_ns;
+ p.total_time_ns = total_time_ns;
+ p.report_fully_drawn_ns = report_fully_drawn_ns;
+
+ return p;
+ }
+
+ static bool UpdateReportFullyDrawn(DbHandle db,
+ int history_id,
+ uint64_t report_fully_drawn_ns)
+ {
+ const char* sql = "UPDATE app_launch_histories "
+ "SET report_fully_drawn_ns = ?1 "
+ "WHERE id = ?2;";
+
+ bool result = DbQueryBuilder::Update(db, sql, report_fully_drawn_ns, history_id);
+
+ if (!result) {
+ LOG(ERROR)<< "Failed to update history_id:"<< history_id
+ << ", report_fully_drawn_ns: " << report_fully_drawn_ns;
+ }
+ return result;
+ }
+
+ int id;
+ int activity_id; // ActivityModel::id
+ Temperature temperature = Temperature::kUninitialized;
+ bool trace_enabled;
+ bool readahead_enabled;
+ std::optional<uint64_t> intent_started_ns;
+ std::optional<uint64_t> total_time_ns;
+ std::optional<uint64_t> report_fully_drawn_ns;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const AppLaunchHistoryModel& p) {
+ os << "AppLaunchHistoryModel{id=" << p.id << ","
+ << "activity_id=" << p.activity_id << ","
+ << "temperature=" << static_cast<int>(p.temperature) << ","
+ << "trace_enabled=" << p.trace_enabled << ","
+ << "readahead_enabled=" << p.readahead_enabled << ","
+ << "intent_started_ns=";
+ if (p.intent_started_ns) {
+ os << *p.intent_started_ns;
+ } else {
+ os << "(nullopt)";
+ }
+ os << ",";
+ os << "total_time_ns=";
+ if (p.total_time_ns) {
+ os << *p.total_time_ns;
+ } else {
+ os << "(nullopt)";
+ }
+ os << ",";
+ os << "report_fully_drawn_ns=";
+ if (p.report_fully_drawn_ns) {
+ os << *p.report_fully_drawn_ns;
+ } else {
+ os << "(nullopt)";
+ }
+ os << "}";
+ return os;
+}
+
+class RawTraceModel : public Model {
+ protected:
+ RawTraceModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+
+ // Return raw_traces, sorted ascending by the id.
+ static std::vector<RawTraceModel> SelectByVersionedComponentName(DbHandle db,
+ VersionedComponentName vcn) {
+ ScopedLockDb lock{db};
+
+ const char* sql =
+ "SELECT raw_traces.id, raw_traces.history_id, raw_traces.file_path "
+ "FROM raw_traces "
+ "INNER JOIN app_launch_histories ON raw_traces.history_id = app_launch_histories.id "
+ "INNER JOIN activities ON activities.id = app_launch_histories.activity_id "
+ "INNER JOIN packages ON packages.id = activities.package_id "
+ "WHERE packages.name = ? AND activities.name = ? AND packages.version = ?"
+ "ORDER BY raw_traces.id ASC";
+
+ DbStatement stmt = DbStatement::Prepare(db,
+ sql,
+ vcn.GetPackage(),
+ vcn.GetActivity(),
+ vcn.GetVersion());
+
+ std::vector<RawTraceModel> results;
+
+ RawTraceModel p{db};
+ while (DbQueryBuilder::SelectOnce(stmt, p.id, p.history_id, p.file_path)) {
+ results.push_back(p);
+ }
+
+ return results;
+ }
+
+ static std::optional<RawTraceModel> SelectByHistoryId(DbHandle db, int history_id) {
+ ScopedLockDb lock{db};
+
+ const char* sql =
+ "SELECT id, history_id, file_path "
+ "FROM raw_traces "
+ "WHERE history_id = ?1 "
+ "LIMIT 1;";
+
+ DbStatement stmt = DbStatement::Prepare(db, sql, history_id);
+
+ RawTraceModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.history_id, p.file_path)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<RawTraceModel> Insert(DbHandle db,
+ int history_id,
+ std::string file_path) {
+ const char* sql = "INSERT INTO raw_traces (history_id, file_path) VALUES (?1, ?2);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db, sql, history_id, file_path);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ RawTraceModel p{db};
+ p.id = *inserted_row_id;
+ p.history_id = history_id;
+ p.file_path = file_path;
+
+ return p;
+ }
+
+ bool Delete() {
+ const char* sql = "DELETE FROM raw_traces WHERE id = ?";
+
+ return DbQueryBuilder::Delete(db(), sql, id);
+ }
+
+ int id;
+ int history_id;
+ std::string file_path;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const RawTraceModel& p) {
+ os << "RawTraceModel{id=" << p.id << ",history_id=" << p.history_id << ",";
+ os << "file_path=" << p.file_path << "}";
+ return os;
+}
+
+class PrefetchFileModel : public Model {
+ protected:
+ PrefetchFileModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ static std::optional<PrefetchFileModel> SelectByVersionedComponentName(
+ DbHandle db,
+ VersionedComponentName vcn) {
+ ScopedLockDb lock{db};
+
+ const char* sql =
+ "SELECT prefetch_files.id, prefetch_files.activity_id, prefetch_files.file_path "
+ "FROM prefetch_files "
+ "INNER JOIN activities ON activities.id = prefetch_files.activity_id "
+ "INNER JOIN packages ON packages.id = activities.package_id "
+ "WHERE packages.name = ? AND activities.name = ? AND packages.version = ?";
+
+ DbStatement stmt = DbStatement::Prepare(db,
+ sql,
+ vcn.GetPackage(),
+ vcn.GetActivity(),
+ vcn.GetVersion());
+
+ PrefetchFileModel p{db};
+
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.activity_id, p.file_path)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::vector<PrefetchFileModel> SelectAll(DbHandle db) {
+ ScopedLockDb lock{db};
+
+ std::string query =
+ "SELECT prefetch_files.id, prefetch_files.activity_id, prefetch_files.file_path "
+ "FROM prefetch_files";
+ DbStatement stmt = DbStatement::Prepare(db, query);
+
+ std::vector<PrefetchFileModel> prefetch_files;
+ PrefetchFileModel p{db};
+ while (DbQueryBuilder::SelectOnce(stmt, p.id, p.activity_id, p.file_path)) {
+ prefetch_files.push_back(p);
+ }
+
+ return prefetch_files;
+ }
+
+ static std::optional<PrefetchFileModel> Insert(DbHandle db,
+ int activity_id,
+ std::string file_path) {
+ const char* sql = "INSERT INTO prefetch_files (activity_id, file_path) VALUES (?1, ?2);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db, sql, activity_id, file_path);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ PrefetchFileModel p{db};
+ p.id = *inserted_row_id;
+ p.activity_id = activity_id;
+ p.file_path = file_path;
+
+ return p;
+ }
+
+ bool Delete() {
+ const char* sql = "DELETE FROM prefetch_files WHERE id = ?";
+
+ return DbQueryBuilder::Delete(db(), sql, id);
+ }
+
+ int id;
+ int activity_id;
+ std::string file_path;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const PrefetchFileModel& p) {
+ os << "PrefetchFileModel{id=" << p.id << ",activity_id=" << p.activity_id << ",";
+ os << "file_path=" << p.file_path << "}";
+ return os;
+}
+
+} // namespace iorap::db
+
+#endif // IORAP_SRC_DB_MODELS_H_
diff --git a/src/inode2filename/data_source.cc b/src/inode2filename/data_source.cc
new file mode 100644
index 0000000..5fcce73
--- /dev/null
+++ b/src/inode2filename/data_source.cc
@@ -0,0 +1,187 @@
+// Copyright (C) 2019 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 "inode2filename/data_source.h"
+
+#include "common/cmd_utils.h"
+#include "inode2filename/inode_resolver.h"
+#include "inode2filename/search_directories.h"
+
+#include <android-base/logging.h>
+
+#include <fstream>
+#include <stdio.h>
+
+namespace rx = rxcpp;
+
+namespace iorap::inode2filename {
+
+std::vector<std::string> ToArgs(DataSourceKind data_source_kind) {
+ const char* value = nullptr;
+
+ switch (data_source_kind) {
+ case DataSourceKind::kDiskScan:
+ value = "diskscan";
+ break;
+ case DataSourceKind::kTextCache:
+ value = "textcache";
+ break;
+ case DataSourceKind::kBpf:
+ value = "bpf";
+ break;
+ }
+
+ std::vector<std::string> args;
+ iorap::common::AppendNamedArg(args, "--data-source", value);
+ return args;
+}
+
+std::vector<std::string> ToArgs(const DataSourceDependencies& deps) {
+ std::vector<std::string> args;
+
+ iorap::common::AppendArgsRepeatedly(args, ToArgs(deps.data_source));
+ // intentionally skip system_call; it does not have a command line equivalent
+ iorap::common::AppendNamedArgRepeatedly(args, "--root", deps.root_directories);
+
+ if (deps.text_cache_filename) {
+ iorap::common::AppendNamedArg(args, "--textcache", *(deps.text_cache_filename));
+ }
+
+ return args;
+}
+
+class DiskScanDataSource : public DataSource {
+ public:
+ DiskScanDataSource(DataSourceDependencies dependencies)
+ : DataSource(std::move(dependencies)) {
+ DCHECK_NE(dependencies_.root_directories.size(), 0u) << "Root directories can't be empty";
+ }
+
+ virtual rxcpp::observable<InodeResult> EmitInodes() const override {
+ SearchDirectories searcher{/*borrow*/dependencies_.system_call};
+ return searcher.ListAllFilenames(dependencies_.root_directories);
+ }
+
+ // Since not all Inodes emitted are the ones searched for, doing additional stat(2) calls here
+ // would be redundant.
+ //
+ // We set the device number to a dummy value here (-1) so that InodeResolver
+ // can fill it in later with stat(2). This is effectively the same thing as always doing
+ // verification.
+ virtual bool ResultIncludesDeviceNumber() const { return false; };
+
+ virtual ~DiskScanDataSource() {}
+};
+
+static inline void LeftTrim(/*inout*/std::string& s) {
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
+ return !std::isspace(ch);
+ }));
+}
+
+class TextCacheDataSource : public DataSource {
+ public:
+ TextCacheDataSource(DataSourceDependencies dependencies)
+ : DataSource(std::move(dependencies)) {
+ DCHECK(dependencies_.text_cache_filename.has_value()) << "Must have text cache filename";
+ }
+
+ virtual rxcpp::observable<InodeResult> EmitInodes() const override {
+ const std::string& file_name = *dependencies_.text_cache_filename;
+
+ return rx::observable<>::create<InodeResult>(
+ [file_name](rx::subscriber<InodeResult> dest) {
+ LOG(VERBOSE) << "TextCacheDataSource (lambda)";
+
+ std::ifstream ifs{file_name};
+
+ if (!ifs.is_open()) {
+ dest.on_error(rxcpp::util::make_error_ptr(
+ std::ios_base::failure("Could not open specified text cache filename")));
+ return;
+ }
+
+ while (dest.is_subscribed() && ifs) {
+ LOG(VERBOSE) << "TextCacheDataSource (read line)";
+ // TODO.
+
+ uint64_t device_number = 0;
+ uint64_t inode_number = 0;
+ uint64_t file_size = 0;
+
+ // Parse lines of form:
+ // "$device_number $inode $filesize $filename..."
+ // This format conforms to system/extras/pagecache/pagecache.py
+
+ ifs >> device_number;
+ ifs >> inode_number;
+ ifs >> file_size;
+ (void)file_size; // Not used in iorapd.
+
+ std::string value_filename;
+ std::getline(/*inout*/ifs, /*out*/value_filename);
+
+ if (value_filename.empty()) {
+ // Ignore empty lines.
+ continue;
+ }
+
+ // getline, unlike ifstream, does not ignore spaces.
+ // There's always at least 1 space in a textcache output file.
+ // However, drop *all* spaces on the left since filenames starting with a space are
+ // ambiguous to us.
+ LeftTrim(/*inout*/value_filename);
+
+ Inode inode = Inode::FromDeviceAndInode(static_cast<dev_t>(device_number),
+ static_cast<ino_t>(inode_number));
+
+ LOG(VERBOSE) << "TextCacheDataSource (on_next) " << inode << "->" << value_filename;
+ dest.on_next(InodeResult::makeSuccess(inode, value_filename));
+ }
+
+ dest.on_completed();
+ }
+ );
+
+ // TODO: plug into the filtering and verification graph.
+ }
+
+ virtual ~TextCacheDataSource() {}
+};
+
+DataSource::DataSource(DataSourceDependencies dependencies)
+ : dependencies_{std::move(dependencies)} {
+ DCHECK(dependencies_.system_call != nullptr);
+}
+
+std::shared_ptr<DataSource> DataSource::Create(DataSourceDependencies dependencies) {
+ switch (dependencies.data_source) {
+ case DataSourceKind::kDiskScan:
+ return std::shared_ptr<DataSource>{new DiskScanDataSource{std::move(dependencies)}};
+ case DataSourceKind::kTextCache:
+ return std::shared_ptr<DataSource>{new TextCacheDataSource{std::move(dependencies)}};
+ case DataSourceKind::kBpf:
+ // TODO: BPF-based data source.
+ LOG(FATAL) << "Not implemented yet";
+ return nullptr;
+ default:
+ LOG(FATAL) << "Invalid data source value";
+ return nullptr;
+ }
+}
+
+void DataSource::StartRecording() {}
+void DataSource::StopRecording() {}
+
+} // namespace iorap::inode2filename
diff --git a/src/inode2filename/data_source.h b/src/inode2filename/data_source.h
new file mode 100644
index 0000000..36c8383
--- /dev/null
+++ b/src/inode2filename/data_source.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2019 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 IORAP_SRC_INODE2FILENAME_DATA_SOURCE_H_
+#define IORAP_SRC_INODE2FILENAME_DATA_SOURCE_H_
+
+#include "inode2filename/inode_result.h"
+#include "inode2filename/system_call.h"
+
+#include <rxcpp/rx.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+namespace iorap::inode2filename {
+
+enum class DataSourceKind {
+ kDiskScan,
+ kTextCache,
+ kBpf,
+};
+
+std::vector<std::string> ToArgs(DataSourceKind data_source_kind);
+
+struct DataSourceDependencies {
+ DataSourceKind data_source = DataSourceKind::kDiskScan;
+ borrowed<SystemCall*> system_call = nullptr;
+
+ // kDiskScan-specific options. Other data sources ignore these fields.
+ std::vector<std::string> root_directories;
+ // kTextCache-specific options. Other data sources ignore these fields.
+ std::optional<std::string> text_cache_filename;
+};
+
+std::vector<std::string> ToArgs(const DataSourceDependencies& deps);
+
+class DataSource : std::enable_shared_from_this<DataSource> {
+ public:
+ static std::shared_ptr<DataSource> Create(DataSourceDependencies dependencies);
+
+ virtual void StartRecording(); // XX: feels like this should be BPF-specific.
+ virtual void StopRecording();
+
+ // Emit all inode->filename mappings (i.e. an infinite lazy list).
+ // The specific order is determined by the extra dependency options.
+ //
+ // The work must terminate if all subscriptions are removed.
+ virtual rxcpp::observable<InodeResult> EmitInodes() const = 0;
+
+ // Does the InodeResult include a valid device number?
+ // If returning false, the InodeResolver fills in the missing device number with stat(2).
+ virtual bool ResultIncludesDeviceNumber() const { return true; };
+
+ protected:
+ virtual ~DataSource() {}
+ DataSource(DataSourceDependencies dependencies);
+ DataSourceDependencies dependencies_;
+};
+
+} // namespace iorap::inode2filename
+
+#endif // IORAP_SRC_INODE2FILENAME_DATA_SOURCE_H_
diff --git a/src/inode2filename/inode.cc b/src/inode2filename/inode.cc
index cdb2325..cc665ea 100644
--- a/src/inode2filename/inode.cc
+++ b/src/inode2filename/inode.cc
@@ -79,3 +79,9 @@ bool Inode::Parse(const std::string& str, Inode* out, std::string* error_msg) {
}
} // namespace iorap::inode2filename
+
+// Ensure our pollution-free aliases match the typedefs in the system headers.
+static_assert(std::is_same_v<iorap::inode2filename::dev_t, dev_t>);
+static_assert(std::is_same_v<iorap::inode2filename::ino_t, ino_t>);
+
+// TODO: consider some tests for major, minor, etc. \ No newline at end of file
diff --git a/src/inode2filename/inode.h b/src/inode2filename/inode.h
index 2dd5611..f336a14 100644
--- a/src/inode2filename/inode.h
+++ b/src/inode2filename/inode.h
@@ -23,22 +23,97 @@
namespace iorap::inode2filename {
+// Avoid polluting headers.
+#if defined(__ANDROID__)
+# if !defined(__LP64__)
+/* This historical accident means that we had a 32-bit dev_t on 32-bit architectures. */
+using dev_t = uint32_t;
+# else
+using dev_t = uint64_t;
+# endif
+using ino_t = unsigned long;
+#else
+# if !defined(__x86_64__)
+using dev_t = unsigned long long;
+using ino_t = unsigned long long;
+# else
+using dev_t = unsigned long;
+using ino_t = unsigned long;
+# endif
+#endif
+
+#ifdef makedev
+#undef makedev
+#endif
+
+/** Combines `major` and `minor` into a device number. */
+constexpr inline dev_t makedev(unsigned int major, unsigned int minor) {
+ return
+ (((major) & 0xfffff000ULL) << 32) | (((major) & 0xfffULL) << 8) |
+ (((minor) & 0xffffff00ULL) << 12) | (((minor) & 0xffULL));
+}
+
+#ifdef major
+#undef major
+#endif
+
+/** Extracts the major part of a device number. */
+constexpr inline unsigned int major(dev_t dev) {
+ return
+ ((unsigned) ((((unsigned long long) (dev) >> 32) & 0xfffff000) | (((dev) >> 8) & 0xfff)));
+}
+
+#ifdef minor
+#undef minor
+#endif
+
+/** Extracts the minor part of a device number. */
+constexpr inline unsigned int minor(dev_t dev) {
+ return
+ ((unsigned) ((((dev) >> 12) & 0xffffff00) | ((dev) & 0xff)));
+};
+// Note: above definitions copied from sysmacros.h, to avoid polluting global namespace in a header.
+
+/*
+ * A convenient datum representing a (dev_t, ino_t) tuple.
+ *
+ * ino_t values may be reused across different devices (e.g. different partitions),
+ * so we need the full tuple to uniquely identify an inode on a system.
+ */
struct Inode {
size_t device_major; // dev_t = makedev(major, minor)
size_t device_minor;
size_t inode; // ino_t = inode
+ // "Major:minor:inode" OR "dev_t@inode"
static bool Parse(const std::string& str, /*out*/Inode* out, /*out*/std::string* error_msg);
- bool operator==(const Inode& rhs) const {
+ constexpr bool operator==(const Inode& rhs) const {
return device_major == rhs.device_major &&
device_minor == rhs.device_minor &&
inode == rhs.inode;
}
- bool operator!=(const Inode& rhs) const {
+ constexpr bool operator!=(const Inode& rhs) const {
return !(*this == rhs);
}
+
+ Inode() = default;
+ constexpr Inode(size_t device_major, size_t device_minor, size_t inode)
+ : device_major{device_major}, device_minor{device_minor}, inode{inode} {
+ }
+
+ static constexpr Inode FromDeviceAndInode(dev_t dev, ino_t inode) {
+ return Inode{major(dev), minor(dev), static_cast<size_t>(inode)};
+ }
+
+ constexpr dev_t GetDevice() const {
+ return makedev(device_major, device_minor);
+ }
+
+ constexpr ino_t GetInode() const {
+ return static_cast<ino_t>(inode);
+ }
};
inline std::ostream& operator<<(std::ostream& os, const Inode& inode) {
@@ -62,4 +137,14 @@ namespace std {
};
} // namespace std
+namespace rxcpp {
+template <class T, typename>
+struct filtered_hash;
+
+// support for the 'distinct' rx operator.
+template <>
+struct filtered_hash<iorap::inode2filename::Inode, void> : std::hash<iorap::inode2filename::Inode> {
+};
+} // namespace rxcpp
+
#endif // IORAP_SRC_INODE2FILENAME_INODE_H_
diff --git a/src/inode2filename/inode_resolver.cc b/src/inode2filename/inode_resolver.cc
new file mode 100644
index 0000000..5e2945d
--- /dev/null
+++ b/src/inode2filename/inode_resolver.cc
@@ -0,0 +1,207 @@
+// Copyright (C) 2019 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 "inode2filename/inode_resolver.h"
+
+#include "common/cmd_utils.h"
+#include "inode2filename/out_of_process_inode_resolver.h"
+#include "inode2filename/search_directories.h"
+
+#include <android-base/logging.h>
+
+#include <fstream>
+#include <stdio.h>
+
+namespace rx = rxcpp;
+
+namespace iorap::inode2filename {
+
+std::vector<std::string> ToArgs(ProcessMode process_mode) {
+ const char* value = nullptr;
+
+ switch (process_mode) {
+ case ProcessMode::kInProcessDirect:
+ value = "in";
+ break;
+ case ProcessMode::kInProcessIpc:
+ value = "in-ipc";
+ break;
+ case ProcessMode::kOutOfProcessIpc:
+ value = "out";
+ break;
+ }
+
+ std::vector<std::string> args;
+ iorap::common::AppendNamedArg(args, "--process-mode", value);
+ return args;
+}
+
+std::vector<std::string> ToArgs(VerifyKind verify_kind) {
+ const char* value = nullptr;
+
+ switch (verify_kind) {
+ case VerifyKind::kNone:
+ value = "none";
+ break;
+ case VerifyKind::kStat:
+ value = "stat";
+ break;
+ }
+
+ std::vector<std::string> args;
+ iorap::common::AppendNamedArg(args, "--verify", value);
+ return args;
+}
+
+std::vector<std::string> ToArgs(const InodeResolverDependencies& deps) {
+ std::vector<std::string> args = ToArgs(*static_cast<const DataSourceDependencies*>(&deps));
+ iorap::common::AppendArgsRepeatedly(args, ToArgs(deps.process_mode));
+ iorap::common::AppendArgsRepeatedly(args, ToArgs(deps.verify));
+
+ return args;
+}
+
+struct InodeResolver::Impl {
+ Impl(InodeResolverDependencies dependencies)
+ : dependencies_{std::move(dependencies)} {
+ DCHECK(dependencies_.system_call != nullptr);
+ data_source_ = DataSource::Create(/*downcast*/dependencies_);
+ }
+ Impl(InodeResolverDependencies dependencies, std::shared_ptr<DataSource> data_source)
+ : dependencies_{std::move(dependencies)} {
+ DCHECK(dependencies_.system_call != nullptr);
+ data_source_ = std::move(data_source);
+ DCHECK(data_source_ != nullptr);
+ }
+ InodeResolverDependencies dependencies_;
+ std::shared_ptr<DataSource> data_source_;
+};
+
+InodeResolver::InodeResolver(InodeResolverDependencies dependencies)
+ : impl_(new InodeResolver::Impl{std::move(dependencies)}) {
+}
+
+InodeResolver::InodeResolver(InodeResolverDependencies dependencies,
+ std::shared_ptr<DataSource> data_source)
+ : impl_(new InodeResolver::Impl{std::move(dependencies), std::move(data_source)}) {
+}
+
+std::shared_ptr<InodeResolver> InodeResolver::Create(InodeResolverDependencies dependencies) {
+ if (dependencies.process_mode == ProcessMode::kInProcessDirect) {
+ return std::shared_ptr<InodeResolver>{
+ new InodeResolver{std::move(dependencies)}};
+ } else if (dependencies.process_mode == ProcessMode::kOutOfProcessIpc) {
+ return std::shared_ptr<InodeResolver>{
+ new OutOfProcessInodeResolver{std::move(dependencies)}};
+ } else {
+ CHECK(false);
+ }
+ return nullptr;
+}
+
+std::shared_ptr<InodeResolver> InodeResolver::Create(InodeResolverDependencies dependencies,
+ std::shared_ptr<DataSource> data_source) {
+ if (dependencies.process_mode == ProcessMode::kInProcessDirect) {
+ return std::shared_ptr<InodeResolver>{
+ new InodeResolver{std::move(dependencies), std::move(data_source)}};
+ } else if (dependencies.process_mode == ProcessMode::kOutOfProcessIpc) {
+ CHECK(false); // directly providing a DataSource only makes sense in-process
+ } else {
+ CHECK(false);
+ }
+ return nullptr;
+}
+
+rxcpp::observable<InodeResult>
+ InodeResolver::FindFilenamesFromInodes(rxcpp::observable<Inode> inodes) const {
+
+ // It's inefficient to search for inodes until the full search list is available,
+ // so first reduce to a vector so we can access all the inodes simultaneously.
+ return inodes.reduce(std::vector<Inode>{},
+ [](std::vector<Inode> vec, Inode inode) {
+ vec.push_back(inode);
+ return vec;
+ },
+ [](std::vector<Inode> v) {
+ return v; // TODO: use an identity function
+ })
+ .flat_map([self=shared_from_this()](std::vector<Inode> vec) {
+ // All borrowed values (e.g. SystemCall) must outlive the observable.
+ return self->FindFilenamesFromInodes(vec);
+ }
+ );
+}
+
+rxcpp::observable<InodeResult>
+InodeResolver::FindFilenamesFromInodes(std::vector<Inode> inodes) const {
+ const DataSource& data_source = *impl_->data_source_;
+ const InodeResolverDependencies& dependencies = impl_->dependencies_;
+
+ // Get lazy list of inodes from the data source.
+ rxcpp::observable<InodeResult> all_inodes = impl_->data_source_->EmitInodes();
+
+ // Filter it according to the source+dependency requirements.
+ // Unsubscribe from 'all_inodes' early if all inodes are matched early.
+ const bool needs_device_number = !data_source.ResultIncludesDeviceNumber();
+ const bool needs_verification = dependencies.verify == VerifyKind::kStat;
+ SearchDirectories search{impl_->dependencies_.system_call};
+ return search.FilterFilenamesForSpecificInodes(all_inodes,
+ inodes,
+ needs_device_number,
+ needs_verification);
+}
+
+rxcpp::observable<InodeResult>
+InodeResolver::EmitAll() const {
+ const DataSource& data_source = *impl_->data_source_;
+ const InodeResolverDependencies& dependencies = impl_->dependencies_;
+
+ // Get lazy list of inodes from the data source.
+ rxcpp::observable<InodeResult> all_inodes = impl_->data_source_->EmitInodes();
+
+ // Apply verification and fill-in missing device numbers.
+ const bool needs_device_number = !data_source.ResultIncludesDeviceNumber();
+ const bool needs_verification = dependencies.verify == VerifyKind::kStat;
+ SearchDirectories search{impl_->dependencies_.system_call};
+ return search.EmitAllFilenames(all_inodes,
+ needs_device_number,
+ needs_verification);
+}
+
+InodeResolver::~InodeResolver() {
+ // std::unique_ptr requires complete types, but we hide the definition in the header.
+ delete impl_;
+ // XX: Does this work if we just force the dtor definition into the .cc file with a unique_ptr?
+}
+
+InodeResolverDependencies& InodeResolver::GetDependencies() {
+ return impl_->dependencies_;
+}
+
+const InodeResolverDependencies& InodeResolver::GetDependencies() const {
+ return impl_->dependencies_;
+}
+
+void InodeResolver::StartRecording() {
+ impl_->data_source_->StartRecording();
+}
+
+void InodeResolver::StopRecording() {
+ impl_->data_source_->StopRecording();
+}
+
+// TODO: refactor more code from search_directories into this file.
+// XX: do we also need a DataSink class? lets see if recording gets more complicated.
+
+} // namespace iorap::inode2filename
diff --git a/src/inode2filename/inode_resolver.h b/src/inode2filename/inode_resolver.h
new file mode 100644
index 0000000..218640f
--- /dev/null
+++ b/src/inode2filename/inode_resolver.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2019 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 IORAP_SRC_INODE2FILENAME_INODE_RESOLVER_H_
+#define IORAP_SRC_INODE2FILENAME_INODE_RESOLVER_H_
+
+#include "common/expected.h"
+#include "inode2filename/data_source.h"
+#include "inode2filename/inode.h"
+#include "inode2filename/system_call.h"
+
+#include <rxcpp/rx.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+namespace iorap::inode2filename {
+
+enum class ProcessMode {
+ // Test modes:
+ kInProcessDirect, // Execute the code directly.
+ kInProcessIpc, // Execute code via IPC layer using multiple threads.
+ // Shipping mode:
+ kOutOfProcessIpc, // Execute code via fork+exec with IPC.
+
+ // Note: in-process system-wide stat(2)/readdir/etc is blocked by selinux.
+ // Attempting to call the test modes will fail with -EPERM.
+ //
+ // Use fork+exec mode in shipping configurations, which spawns inode2filename
+ // as a separate command.
+};
+
+enum class VerifyKind {
+ kNone,
+ kStat,
+};
+
+std::vector<std::string> ToArgs(VerifyKind verify_kind);
+
+struct InodeResolverDependencies : public DataSourceDependencies {
+ ProcessMode process_mode = ProcessMode::kInProcessDirect;
+ VerifyKind verify{VerifyKind::kStat}; // Filter out results that aren't up-to-date with stat(2) ?
+};
+
+std::vector<std::string> ToArgs(const InodeResolverDependencies& deps);
+
+// Create an rx-observable chain that allows searching for inode->filename mappings given
+// a set of inode keys.
+class InodeResolver : public std::enable_shared_from_this<InodeResolver> {
+ public:
+ static std::shared_ptr<InodeResolver> Create(InodeResolverDependencies dependencies,
+ std::shared_ptr<DataSource> data_source); // nonnull
+
+ // Convenience function for above: Uses DataSource::Create for the data-source.
+ static std::shared_ptr<InodeResolver> Create(InodeResolverDependencies dependencies);
+
+ // Search the associated data source to map each inode in 'inodes' to a file path.
+ //
+ // Observes DataSource::EmitInodes(), which is unsubscribed from early once all inodes are found.
+ //
+ // Notes:
+ // * Searching does not begin until all 'inodes' are observed to avoid rescanning.
+ // * If the observable is unsubscribed to prior to completion, searching will halt.
+ //
+ // Post-condition: All emitted results are in inodes, and all inodes are in emitted results.
+ rxcpp::observable<InodeResult>
+ FindFilenamesFromInodes(rxcpp::observable<Inode> inodes) const;
+ // TODO: feels like we could turn this into a general helper?
+ // Convenience function for above.
+ virtual rxcpp::observable<InodeResult>
+ FindFilenamesFromInodes(std::vector<Inode> inodes) const;
+
+ // Enumerate *all* inodes available from the data source, associating it with a filepath.
+ //
+ // Depending on the data source (e.g. diskscan), it can take a very long time for this observable
+ // to complete. The intended use-case is for development/debugging, not for production.
+ //
+ // Observes DataSource::EmitInodes() until it reaches #on_completed.
+ //
+ // Notes:
+ // * If the observable is unsubscribed to prior to completion, searching will halt.
+ virtual rxcpp::observable<InodeResult>
+ EmitAll() const;
+
+ // Notifies the DataSource to begin recording.
+ // Some DataSources may be continuously refreshing, but only if recording is enabled.
+ // To get the most up-to-date data, toggle recording before reading the inodes out.
+ void StartRecording(); // XX: feels like this should be BPF-specific.
+
+ // Notifies the DataSource to stop recording.
+ // Some DataSources may be continuously refreshing, but only if recording is enabled.
+ // The snapshot of data returned by e.g. #EmitAll would then not change outside of recording.
+ void StopRecording();
+
+ virtual ~InodeResolver();
+ private:
+ struct Impl;
+ Impl* impl_;
+
+ protected:
+ InodeResolver(InodeResolverDependencies dependencies);
+ InodeResolver(InodeResolverDependencies dependencies, std::shared_ptr<DataSource> data_source);
+ InodeResolverDependencies& GetDependencies();
+ const InodeResolverDependencies& GetDependencies() const;
+};
+
+}
+
+#endif // IORAP_SRC_INODE2FILENAME_INODE_RESOLVER_H_
diff --git a/src/inode2filename/inode_result.cc b/src/inode2filename/inode_result.cc
new file mode 100644
index 0000000..139b038
--- /dev/null
+++ b/src/inode2filename/inode_result.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2019 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 "inode2filename/inode_result.h"
+
+#include <string.h>
+
+namespace iorap::inode2filename {
+
+std::optional<std::string_view> InodeResult::ErrorMessage() const {
+ if (data) {
+ return std::nullopt;
+ }
+
+ const int err_no = data.error();
+ return std::string_view{
+ [=]() -> const char * {
+ switch (err_no) {
+ case InodeResult::kCouldNotFindFilename:
+ return "Could not find filename";
+ case InodeResult::kVerificationFailed:
+ return "Verification failed";
+ default:
+ return strerror(err_no);
+ }
+ }()
+ };
+}
+
+std::ostream& operator<<(std::ostream& os, const InodeResult& result) {
+ os << "InodeResult{";
+ if (result) {
+ os << "OK,";
+ } else {
+ os << "ERR,";
+ }
+
+ os << result.inode << ",";
+
+ if (result) {
+ os << "\"" << result.data.value() << "\"";
+ } else {
+ os << result.data.error();
+ os << " (" << *result.ErrorMessage() << ")";
+ }
+
+ os << "}";
+ return os;
+}
+
+} // namespace iorap::inode2filename \ No newline at end of file
diff --git a/src/inode2filename/inode_result.h b/src/inode2filename/inode_result.h
new file mode 100644
index 0000000..df352fb
--- /dev/null
+++ b/src/inode2filename/inode_result.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2019 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 IORAP_SRC_INODE2FILENAME_INODE_RESULT_H_
+#define IORAP_SRC_INODE2FILENAME_INODE_RESULT_H_
+
+#include "common/expected.h"
+#include "inode2filename/inode.h"
+#include "inode2filename/inode_result.h"
+#include "inode2filename/system_call.h"
+
+#include <rxcpp/rx.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+namespace iorap::inode2filename {
+
+// Tuple of (Inode -> (Filename|Errno))
+struct InodeResult {
+ // We set this error when all root directories have been searched and
+ // yet we still could not find a corresponding filename for the inode under search.
+ static constexpr int kCouldNotFindFilename = ENOKEY;
+
+ // An initial inode->filename mapping was found, but subsequent verification for it failed.
+ static constexpr int kVerificationFailed = EKEYEXPIRED;
+
+ // There is always an inode, but sometimes we may fail to resolve the filename.
+ Inode inode;
+ // Value: Contains the filename (with a root directory as a prefix).
+ // Error: Contains the errno, usually one of the above, otherwise some system error.
+ iorap::expected<std::string /*filename*/, int /*errno*/> data;
+
+ static InodeResult makeSuccess(Inode inode, std::string filename) {
+ return InodeResult{inode, std::move(filename)};
+ }
+
+ static InodeResult makeFailure(Inode inode, int err_no) {
+ return InodeResult{inode, iorap::unexpected{err_no}};
+ }
+
+ constexpr explicit operator bool() const {
+ return data.has_value();
+ }
+
+ constexpr bool operator==(const InodeResult& other) const {
+ if (inode == other.inode) {
+ if (data && other.data) {
+ return *data == *other.data;
+ } else if (!data && !other.data) {
+ return data.error() == other.data.error();
+ }
+ // TODO: operator== for expected
+ }
+ return false;
+ }
+
+ constexpr bool operator!=(const InodeResult& other) const {
+ return !(*this == other);
+ }
+
+ // Returns a human-readable error message, or 'nullopt' if there was no error.
+ std::optional<std::string_view> ErrorMessage() const;
+};
+
+std::ostream& operator<<(std::ostream& os, const InodeResult& result);
+
+} // namespace iorap::inode2filename
+
+#endif // IORAP_SRC_INODE2FILENAME_INODE_RESULT_H_ \ No newline at end of file
diff --git a/src/inode2filename/main.cc b/src/inode2filename/main.cc
index 2da364c..38d6eab 100644
--- a/src/inode2filename/main.cc
+++ b/src/inode2filename/main.cc
@@ -14,12 +14,18 @@
#include "common/debug.h"
#include "common/expected.h"
-#include "inode2filename/search_directories.h"
+#include "inode2filename/inode_resolver.h"
-using namespace iorap::inode2filename; // NOLINT
+#include <android-base/strings.h>
+
+#include <iostream>
+#include <fstream>
+#include <string_view>
#if defined(IORAP_INODE2FILENAME_MAIN)
+namespace iorap::inode2filename {
+
void Usage(char** argv) {
std::cerr << "Usage: " << argv[0] << " <options> <<inode_syntax>> [inode_syntax1 inode_syntax2 ...]" << std::endl;
std::cerr << "" << std::endl;
@@ -28,25 +34,174 @@ void Usage(char** argv) {
std::cerr << " the program will terminate." << std::endl;
std::cerr << "" << std::endl;
std::cerr << " Inode syntax: ('dev_t@inode' | 'major:minor:inode')" << std::endl;
+ std::cerr << "" << std::endl; // CLI-only flags.
std::cerr << " --help,-h Print this Usage." << std::endl;
- std::cerr << " --root,-r Add root directory (default '.'). Repeatable." << std::endl;
std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
+ std::cerr << "" << std::endl; // General flags.
+ std::cerr << " --all,-a Enumerate all inode->filename mappings in the dataset (default off)." << std::endl;
+ std::cerr << " All <<inode_syntaxN>> arguments are ignored." << std::endl;
+ std::cerr << " --data-source=, Choose a data source (default 'diskscan')." << std::endl;
+ std::cerr << " -ds " << std::endl;
+ std::cerr << " diskscan Scan disk recursively using readdir." << std::endl;
+ std::cerr << " textcache Use the file from the '--output-format=text'." << std::endl;
+ std::cerr << " bpf Query kernel BPF maps (experimental)." << std::endl;
+ std::cerr << " --output=,-o Choose an output file (default 'stdout')." << std::endl;
+ std::cerr << " --output-format=, Choose an output format (default 'log')." << std::endl;
+ std::cerr << " -of " << std::endl;
+ std::cerr << " log Log human-readable, non-parsable format to stdout+logcat." << std::endl;
+ std::cerr << " textcache Results are in the same format as system/extras/pagecache." << std::endl;
+ std::cerr << " ipc Results are in a binary inter-process communications format" << std::endl;
+ std::cerr << " --process-mode=, Choose a process mode (default 'in'). Test-oriented." << std::endl;
+ std::cerr << " -pm " << std::endl;
+ std::cerr << " in Use a single process to do the work in." << std::endl;
+ std::cerr << " out Out-of-process work (forks into a -pm=in)." << std::endl;
+ std::cerr << " --verify=,-vy Verification modes for the data source (default 'stat')." << std::endl;
+ std::cerr << " stat Use stat(2) call to validate data inodes are up-to-date. " << std::endl;
+ std::cerr << " none Trust that the data-source is up-to-date without checking." << std::endl;
+ std::cerr << "" << std::endl; // --data-source=<?> specific flags.
+ std::cerr << " Data-source-specific commands:" << std::endl;
+ std::cerr << " --data-source=diskscan" << std::endl;
+ std::cerr << " --root=,-r Add root directory (default '.'). Repeatable." << std::endl;
+ std::cerr << " --data-source=textcache" << std::endl;
+ std::cerr << " --textcache=,-tc Name of file that contains the textcache." << std::endl;
+ std::cerr << "" << std::endl; // Programmatic flags.
+ std::cerr << " --in-fd=# Input file descriptor. Default input is from argv." << std::endl;
+ std::cerr << " --out-fd=# Output file descriptor. Default stdout." << std::endl;
exit(1);
}
-static fruit::Component<SearchDirectories> GetSearchDirectoriesComponent() {
+static fruit::Component<SystemCall> GetSystemCallComponent() {
return fruit::createComponent().bind<SystemCall, SystemCallImpl>();
}
+std::optional<DataSourceKind> ParseDataSourceKind(std::string_view str) {
+ if (str == "diskscan") {
+ return DataSourceKind::kDiskScan;
+ } else if (str == "textcache") {
+ return DataSourceKind::kTextCache;
+ } else if (str == "bpf") {
+ return DataSourceKind::kBpf;
+ }
+ return std::nullopt;
+}
+
+enum class OutputFormatKind {
+ kLog,
+ kTextCache,
+ kIpc,
+};
+
+std::optional<OutputFormatKind> ParseOutputFormatKind(std::string_view str) {
+ if (str == "log") {
+ return OutputFormatKind::kLog;
+ } else if (str == "textcache") {
+ return OutputFormatKind::kTextCache;
+ } else if (str == "ipc") {
+ return OutputFormatKind::kIpc;
+ }
+ return std::nullopt;
+}
+
+std::optional<VerifyKind> ParseVerifyKind(std::string_view str) {
+ if (str == "none") {
+ return VerifyKind::kNone;
+ } else if (str == "stat") {
+ return VerifyKind::kStat;
+ }
+ return std::nullopt;
+}
+
+std::optional<ProcessMode> ParseProcessMode(std::string_view str) {
+ if (str == "in") {
+ return ProcessMode::kInProcessDirect;
+ } else if (str == "out") {
+ return ProcessMode::kOutOfProcessIpc;
+ }
+ return std::nullopt;
+}
+
+bool StartsWith(std::string_view haystack, std::string_view needle) {
+ return haystack.size() >= needle.size()
+ && haystack.compare(0, needle.size(), needle) == 0;
+}
+
+bool EndsWith(std::string_view haystack, std::string_view needle) {
+ return haystack.size() >= needle.size()
+ && haystack.compare(haystack.size() - needle.size(), haystack.npos, needle) == 0;
+}
+
+bool StartsWithOneOf(std::string_view haystack,
+ std::string_view needle,
+ std::string_view needle2) {
+ return StartsWith(haystack, needle) || StartsWith(haystack, needle2);
+}
+
+enum ParseError {
+ kParseSkip,
+ kParseFailed,
+};
+
+std::optional<std::string> ParseNamedArgument(std::initializer_list<std::string> names,
+ std::string argstr,
+ std::optional<std::string> arg_next,
+ /*inout*/
+ int* arg_pos) {
+ for (const std::string& name : names) {
+ {
+ // Try parsing only 'argstr' for '--foo=bar' type parameters.
+ std::vector<std::string> split_str = ::android::base::Split(argstr, "=");
+ if (split_str.size() >= 2) {
+ /*
+ std::cerr << "ParseNamedArgument(name=" << name << ", argstr='"
+ << argstr << "')" << std::endl;
+ */
+
+ if (split_str[0] + "=" == name) {
+ return split_str[1];
+ }
+ }
+ }
+ //if (EndsWith(name, "=")) {
+ // continue;
+ /*} else */ {
+ // Try parsing 'argstr arg_next' for '-foo bar' type parameters.
+ if (argstr == name) {
+ ++(*arg_pos);
+
+ if (arg_next) {
+ return arg_next;
+ } else {
+ // Missing argument, e.g. '-foo' was the last token in the argv.
+ std::cerr << "Missing " << name << " flag value." << std::endl;
+ exit(1);
+ }
+ }
+ }
+ }
+
+ return std::nullopt;
+}
+
int main(int argc, char** argv) {
android::base::InitLogging(argv);
android::base::SetLogger(android::base::StderrLogger);
+ bool all = false;
bool wait_for_keystroke = false;
bool enable_verbose = false;
std::vector<std::string> root_directories;
std::vector<Inode> inode_list;
+ int recording_time_sec = 0;
+
+ DataSourceKind data_source = DataSourceKind::kDiskScan;
+ OutputFormatKind output_format = OutputFormatKind::kLog;
+ VerifyKind verify = VerifyKind::kStat;
+ ProcessMode process_mode = ProcessMode::kInProcessDirect;
+
+ std::optional<std::string> output_filename;
+ std::optional<int /*fd*/> in_fd, out_fd; // input-output file descriptors [for fork+exec].
+ std::optional<std::string> text_cache_filename;
if (argc == 1) {
Usage(argv);
@@ -54,22 +209,80 @@ int main(int argc, char** argv) {
for (int arg = 1; arg < argc; ++arg) {
std::string argstr = argv[arg];
- bool has_arg_next = (arg+1)<argc;
- std::string arg_next = has_arg_next ? argv[arg+1] : "";
+ std::optional<std::string> arg_next;
+ if ((arg + 1) < argc) {
+ arg_next = argv[arg+1];
+ }
if (argstr == "--help" || argstr == "-h") {
Usage(argv);
- } else if (argstr == "--root" || argstr == "-r") {
- if (!has_arg_next) {
- std::cerr << "Missing --root <value>" << std::endl;
- return 1;
- }
- root_directories.push_back(arg_next);
- ++arg;
+ } else if (auto val = ParseNamedArgument({"--root=", "-r"}, argstr, arg_next, /*inout*/&arg);
+ val) {
+ root_directories.push_back(*val);
} else if (argstr == "--verbose" || argstr == "-v") {
enable_verbose = true;
} else if (argstr == "--wait" || argstr == "-w") {
wait_for_keystroke = true;
+ }
+ else if (argstr == "--all" || argstr == "-a") {
+ all = true;
+ } else if (auto val = ParseNamedArgument({"--data-source=", "-ds"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto ds = ParseDataSourceKind(*val);
+ if (!ds) {
+ std::cerr << "Invalid --data-source=<value>" << std::endl;
+ return 1;
+ }
+ data_source = *ds;
+ } else if (auto val = ParseNamedArgument({"--output=", "-o"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ output_filename = *val;
+ } else if (auto val = ParseNamedArgument({"--process-mode=", "-pm"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto pm = ParseProcessMode(*val);
+ if (!pm) {
+ std::cerr << "Invalid --process-mode=<value>" << std::endl;
+ return 1;
+ }
+ process_mode = *pm;
+ }
+ else if (auto val = ParseNamedArgument({"--output-format=", "-of"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto of = ParseOutputFormatKind(*val);
+ if (!of) {
+ std::cerr << "Missing --output-format=<value>" << std::endl;
+ return 1;
+ }
+ output_format = *of;
+ } else if (auto val = ParseNamedArgument({"--verify=", "-vy="},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ auto vy = ParseVerifyKind(*val);
+ if (!vy) {
+ std::cerr << "Invalid --verify=<value>" << std::endl;
+ return 1;
+ }
+ verify = *vy;
+ } else if (auto val = ParseNamedArgument({"--textcache=", "-tc"},
+ argstr,
+ arg_next,
+ /*inout*/&arg);
+ val) {
+ text_cache_filename = *val;
} else {
Inode maybe_inode{};
@@ -94,10 +307,23 @@ int main(int argc, char** argv) {
root_directories.push_back(".");
}
- if (inode_list.size() == 0) {
- DCHECK_EQ(true, false);
- std::cerr << "Provide at least one inode." << std::endl;
+ if (inode_list.size() == 0 && !all) {
+ std::cerr << "Provide at least one inode. Or use --all to dump everything." << std::endl;
return 1;
+ } else if (all && inode_list.size() > 0) {
+ std::cerr << "[WARNING]: --all flag ignores all inodes passed on command line." << std::endl;
+ }
+
+ std::ofstream fout;
+ if (output_filename) {
+ fout.open(*output_filename);
+ if (!fout) {
+ std::cerr << "Failed to open output file for writing: \"" << *output_filename << "\"";
+ return 1;
+ }
+ } else {
+ fout.open("/dev/null"); // have to open *something* otherwise rdbuf fails.
+ fout.basic_ios<char>::rdbuf(std::cout.rdbuf());
}
if (enable_verbose) {
@@ -109,7 +335,12 @@ int main(int argc, char** argv) {
for (auto& inode_num : inode_list) {
LOG(VERBOSE) << "Searching for inode " << inode_num;
}
+
+ LOG(VERBOSE) << "Dumping all inodes? " << all;
}
+ // else use
+ // $> ANDROID_LOG_TAGS='*:d' iorap.inode2filename <args>
+ // which will enable arbitrary log levels.
// Useful to attach a debugger...
// 1) $> inode2filename -w <args>
@@ -120,33 +351,90 @@ int main(int argc, char** argv) {
std::cin >> wait_for_keystroke;
}
- fruit::Injector<SearchDirectories> injector(GetSearchDirectoriesComponent);
- SearchDirectories* search_directories = injector.get<SearchDirectories*>();
+ fruit::Injector<SystemCall> injector(GetSystemCallComponent);
+
+ InodeResolverDependencies ir_dependencies;
+ // Passed from command-line.
+ ir_dependencies.data_source = data_source;
+ ir_dependencies.process_mode = process_mode;
+ ir_dependencies.root_directories = root_directories;
+ ir_dependencies.text_cache_filename = text_cache_filename;
+ ir_dependencies.verify = verify;
+ // Hardcoded.
+ ir_dependencies.system_call = injector.get<SystemCall*>();
+
+ std::shared_ptr<InodeResolver> inode_resolver =
+ InodeResolver::Create(ir_dependencies);
- auto/*observable[2]*/ [inode_results, connectable] =
- search_directories->FindFilenamesFromInodesPair(
- std::move(root_directories),
- std::move(inode_list),
- SearchMode::kInProcessDirect);
+ inode_resolver->StartRecording();
+ sleep(recording_time_sec); // TODO: add cli flag for this when we add something that needs it.
+ inode_resolver->StopRecording();
- int return_code = 1;
- inode_results.subscribe([&return_code](const InodeResult& result) {
+ auto/*observable<InodeResult>*/ inode_results = all
+ ? inode_resolver->EmitAll()
+ : inode_resolver->FindFilenamesFromInodes(std::move(inode_list));
+
+ int return_code = 2;
+ inode_results.subscribe(
+ /*on_next*/[&return_code, output_format, &fout](const InodeResult& result) {
if (result) {
- LOG(DEBUG) << "Inode match: " << result.inode << ", " << result.data.value();
- std::cout << "Inode match: " << result.inode << ", " << result.data.value() << std::endl;
+ LOG(DEBUG) << "Inode match: " << result;
+ if (output_format == OutputFormatKind::kLog) {
+ fout << "\033[1;32m[OK]\033[0m "
+ << result.inode
+ << " \"" << result.data.value() << "\"" << std::endl;
+ } else if (output_format == OutputFormatKind::kIpc) {
+ fout << "K "
+ << result.inode
+ << " " << result.data.value() << std::endl;
+ } else if (output_format == OutputFormatKind::kTextCache) {
+ // Same format as TextCacheDataSource (system/extras/pagecache/pagecache.py -d)
+ // "$device_number $inode $filesize $filename..."
+ const Inode& inode = result.inode;
+ fout << inode.GetDevice() << " "
+ << inode.GetInode()
+ << " -1 " // always -1 for filesize, since we don't track what it is.
+ << result.data.value() << "\n"; // don't use endl which flushes, for faster writes.
+ } else {
+ LOG(FATAL) << "Not implemented this kind of --output-format";
+ }
+
return_code = 0;
} else {
- LOG(WARNING) << "Failed to match inode: " << result.inode;
+ LOG(DEBUG) << "Failed to match inode: " << result;
+ if (output_format == OutputFormatKind::kLog) {
+ fout << "\033[1;31m[ERR]\033[0m "
+ << result.inode
+ << " '" << *result.ErrorMessage() << "'" << std::endl;
+ } else if (output_format == OutputFormatKind::kIpc) {
+ fout << "E "
+ << result.inode
+ << " " << result.data.error() << std::endl;
+ }
+ else if (output_format == OutputFormatKind::kTextCache) {
+ // Don't add bad results to the textcache. They are dropped.
+ } else {
+ LOG(FATAL) << "Not implemented this kind of --output-format";
+ }
}
+ }, /*on_error*/[&return_code](rxcpp::util::error_ptr error) {
+ // Usually occurs very early on before we see the first result.
+ // In this case the error is terminal so we just end up exiting out soon.
+ return_code = 3;
+ LOG(ERROR) << "Critical error: " << rxcpp::util::what(error);
});
- // Normally #subscribe would start emitting items immediately, but this does nothing yet
- // because one of the nodes in the flow graph was published. Published streams make the entire
- // downstream inert until #connect is called.
- connectable->connect();
-
- // 0 -> found at least a single match, 1 -> could not find any matches.
+ // 0 -> found at least a single match,
+ // 1 -> bad parameters,
+ // 2 -> could not find any matches,
+ // 3 -> rxcpp on_error.
return return_code;
}
+} // namespace iorap::inode2filename
+
+int main(int argc, char** argv) {
+ return ::iorap::inode2filename::main(argc, argv);
+}
+
#endif
diff --git a/src/inode2filename/out_of_process_inode_resolver.cc b/src/inode2filename/out_of_process_inode_resolver.cc
new file mode 100644
index 0000000..88ce6e0
--- /dev/null
+++ b/src/inode2filename/out_of_process_inode_resolver.cc
@@ -0,0 +1,425 @@
+// 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 "common/cmd_utils.h"
+#include "inode2filename/inode_resolver.h"
+#include "inode2filename/out_of_process_inode_resolver.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+
+#include <sstream>
+#include <stdio.h>
+#include <unistd.h>
+
+namespace rx = rxcpp;
+
+namespace iorap::inode2filename {
+
+using ::android::base::unique_fd;
+using ::android::base::borrowed_fd;
+
+static const char* GetCommandFileName() {
+ // Avoid ENOENT by execve by specifying the absolute path of inode2filename.
+#ifdef __ANDROID__
+ return "/system/bin/iorap.inode2filename";
+#else
+ static const char* file_name = nullptr;
+
+ if (file_name == nullptr) {
+ char* out_dir = getenv("ANDROID_HOST_OUT");
+ static std::string file_name_str = out_dir;
+ if (out_dir != nullptr) {
+ file_name_str += "/bin/";
+ } else {
+ // Assume it's in the same directory as the binary we are in.
+ std::string self_path;
+ CHECK(::android::base::Readlink("/proc/self/exe", /*out*/&self_path));
+
+ std::string self_dir = ::android::base::Dirname(self_path);
+ file_name_str = self_dir + "/";
+ }
+ file_name_str += "iorap.inode2filename";
+
+ file_name = file_name_str.c_str();
+ }
+
+ return file_name;
+#endif
+}
+
+std::error_code ErrorCodeFromErrno() {
+ int err = errno;
+ return std::error_code(err, std::system_category());
+}
+
+std::ios_base::failure IosBaseFailureWithErrno(const char* message) {
+ std::error_code ec = ErrorCodeFromErrno();
+ return std::ios_base::failure(message, ec);
+}
+
+static constexpr bool kDebugFgets = false;
+
+// This always contains the 'newline' character at the end of the string.
+// If there is not, the string is both empty and we hit EOF (or an error occurred).
+std::string FgetsWholeLine(FILE* stream,
+ bool* eof) {
+ DCHECK(stream != nullptr);
+ DCHECK(eof != nullptr);
+
+ char buf[1024];
+
+ std::string str;
+ *eof = false;
+
+ while (true) {
+ memset(buf, '\0', sizeof(buf));
+
+ char* out = fgets(&buf[0], sizeof(buf), stream);
+
+ if (out == nullptr) {
+ // either EOF or error.
+
+ *eof = true;
+ if (feof(stream)) {
+ return str;
+ } else {
+ // error! :(
+ PLOG(ERROR) << "failed to fgets";
+ return str;
+ }
+ }
+
+ if (kDebugFgets) {
+ std::string dbg;
+
+ for (size_t i = 0; i < sizeof(buf); ++i) {
+ if (buf[i] == '\0') {
+ break;
+ }
+
+ int val = buf[i];
+
+ dbg += "," + std::to_string(val);
+ }
+
+ LOG(DEBUG) << "fgets ascii: " << dbg;
+ }
+
+ str += buf;
+
+ // fgets always reads at most count-1 characters.
+ // the last character is always '\0'
+ // the second-to-last character would be \n if we read the full line,
+ // and any other character otherwise.
+ if (!str.empty() && str.back() == '\n') {
+ // we read the whole line: do not need to call fgets again.
+ break;
+ }
+ }
+
+ return str;
+}
+
+static inline void LeftTrim(/*inout*/std::string& s) {
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
+ return !std::isspace(ch);
+ }));
+}
+
+// Parses an --output-format=ipc kind of line into an InodeResult.
+// Returns nullopt if parsing failed.
+std::optional<InodeResult> ParseFromLine(const std::string& line) {
+ // inode <- INT:INT:INT
+ // line_ok <- 'K ' inode ' ' STRING
+ // line_err <- 'E ' inode ' ' INT
+ //
+ // result <- line_ok | line_err
+
+ std::stringstream ss{line};
+
+ bool result_ok = false;
+
+ std::string ok_or_error;
+ ss >> ok_or_error;
+
+ if (ss.fail()) {
+ return std::nullopt;
+ }
+
+ if (ok_or_error == "K") {
+ result_ok = true;
+ } else if (ok_or_error == "E") {
+ result_ok = false;
+ } else {
+ return std::nullopt;
+ }
+
+ std::string inode_s;
+ ss >> inode_s;
+
+ if (ss.fail()) {
+ return std::nullopt;
+ }
+
+ Inode inode;
+
+ std::string inode_parse_error_msg;
+ if (!Inode::Parse(inode_s, /*out*/&inode, /*out*/&inode_parse_error_msg)) {
+ return std::nullopt;
+ }
+
+ if (result_ok == false) {
+ int error_code;
+ ss >> error_code;
+
+ if (ss.fail()) {
+ return std::nullopt;
+ }
+
+ return InodeResult::makeFailure(inode, error_code);
+ } else if (result_ok == true) {
+ std::string rest_of_line;
+
+ // parse " string with potential spaces[\n]"
+ // into "string with potential spaces"
+ std::getline(/*inout*/ss, /*out*/rest_of_line);
+ LeftTrim(/*inout*/rest_of_line);
+
+ if (ss.fail()) {
+ return std::nullopt;
+ }
+
+ const std::string& file_name = rest_of_line;
+
+ return InodeResult::makeSuccess(inode, file_name);
+ }
+
+ return std::nullopt;
+}
+
+struct OutOfProcessInodeResolver::Impl {
+ Impl() {
+ }
+
+ private:
+ // Create the argv we will pass to the forked inode2filename, corresponds to #EmitAll.
+ std::vector<std::string> CreateArgvAll(const InodeResolverDependencies& deps) const {
+ std::vector<std::string> argv;
+ argv.push_back("--all");
+
+ return CreateArgv(deps, std::move(argv));
+ }
+
+ // Create the argv we will pass to the forked inode2filename, corresponds to
+ // #FindFilenamesFromInodes.
+ std::vector<std::string> CreateArgvFind(const InodeResolverDependencies& deps,
+ const std::vector<Inode>& inodes) const {
+ std::vector<std::string> argv;
+ iorap::common::AppendArgsRepeatedly(argv, inodes);
+
+ return CreateArgv(deps, std::move(argv));
+ }
+
+ std::vector<std::string> CreateArgv(const InodeResolverDependencies& deps,
+ std::vector<std::string> append_argv) const {
+ InodeResolverDependencies deps_oop = deps;
+ deps_oop.process_mode = ProcessMode::kInProcessDirect;
+
+ std::vector<std::string> argv = ToArgs(deps_oop);
+
+ argv.push_back("--output-format=ipc");
+
+ if (iorap::common::GetBoolEnvOrProperty("iorap.inode2filename.log.verbose", false)) {
+ argv.push_back("--verbose");
+ }
+
+ iorap::common::AppendArgsRepeatedly(argv, std::move(append_argv));
+
+ return argv;
+ }
+
+ public:
+ // fork+exec into inode2filename with 'inodes' as the search list.
+ // Each result is parsed into a dest#on_next(result).
+ // If a fatal error occurs, dest#on_error is called once and no other callbacks are called.
+ void EmitFromCommandFind(rxcpp::subscriber<InodeResult>& dest,
+ const InodeResolverDependencies& deps,
+ const std::vector<Inode>& inodes) {
+ // Trivial case: complete immediately.
+ // Executing inode2filename with empty search list will just print the --help menu.
+ if (inodes.empty()) {
+ dest.on_completed();
+ }
+
+ std::vector<std::string> argv = CreateArgvFind(deps, inodes);
+ EmitFromCommandWithArgv(/*inout*/dest, std::move(argv), inodes.size());
+ }
+
+ // fork+exec into inode2filename with --all (listing *all* inodes).
+ // Each result is parsed into a dest#on_next(result).
+ // If a fatal error occurs, dest#on_error is called once and no other callbacks are called.
+ void EmitFromCommandAll(rxcpp::subscriber<InodeResult>& dest,
+ const InodeResolverDependencies& deps) {
+ std::vector<std::string> argv = CreateArgvAll(deps);
+ EmitFromCommandWithArgv(/*inout*/dest, std::move(argv), /*result_count*/std::nullopt);
+ }
+
+ private:
+ void EmitFromCommandWithArgv(rxcpp::subscriber<InodeResult>& dest,
+ std::vector<std::string> argv_vec,
+ std::optional<size_t> result_count) {
+ unique_fd pipe_reader, pipe_writer;
+ if (!::android::base::Pipe(/*out*/&pipe_reader, /*out*/&pipe_writer)) {
+ dest.on_error(
+ rxcpp::util::make_error_ptr(
+ IosBaseFailureWithErrno("Failed to create out-going pipe for inode2filename")));
+ return;
+ }
+
+ pid_t child = fork();
+ if (child == -1) {
+ dest.on_error(
+ rxcpp::util::make_error_ptr(
+ IosBaseFailureWithErrno("Failed to fork process for inode2filename")));
+ return;
+ } else if (child > 0) { // we are the caller of this function
+ LOG(DEBUG) << "forked into a process for inode2filename , pid = " << child;
+ } else {
+ // we are the child that was forked
+
+ const char* kCommandFileName = GetCommandFileName();
+
+ std::stringstream argv; // for debugging.
+ for (std::string arg : argv_vec) {
+ argv << arg << ' ';
+ }
+ LOG(DEBUG) << "fork+exec: " << kCommandFileName << " " << argv.str();
+
+ // Redirect only stdout. stdin is unused, stderr is same as parent.
+ if (dup2(pipe_writer.get(), STDOUT_FILENO) == -1) {
+ // Trying to call #on_error does not make sense here because we are in a forked process,
+ // the only thing we can do is crash definitively.
+ PLOG(FATAL) << "Failed to dup2 for inode2filename";
+ }
+
+ std::unique_ptr<const char *[]> argv_ptr =
+ common::VecToArgv(kCommandFileName, argv_vec);
+
+ if (execve(kCommandFileName,
+ (char **)argv_ptr.get(),
+ /*envp*/nullptr) == -1) {
+ // Trying to call #on_error does not make sense here because we are in a forked process,
+ // the only thing we can do is crash definitively.
+ PLOG(FATAL) << "Failed to execve process for inode2filename";
+ }
+ // This should never return.
+ }
+
+ // Immediately close the writer end of the pipe because we never use it.
+ pipe_writer.reset();
+
+ // Convert pipe(reader) file descriptor into FILE*.
+ std::unique_ptr<FILE, int(*)(FILE*)> file_reader{
+ ::android::base::Fdopen(std::move(pipe_reader), /*mode*/"r"), fclose};
+ if (!file_reader) {
+ dest.on_error(
+ rxcpp::util::make_error_ptr(
+ IosBaseFailureWithErrno("Failed to fdopen for inode2filename")));
+ return;
+ }
+
+ size_t actual_result_count = 0;
+
+ bool file_eof = false;
+ while (!file_eof) {
+ std::string inode2filename_line = FgetsWholeLine(file_reader.get(), /*out*/&file_eof);
+
+ if (inode2filename_line.empty()) {
+ if (!file_eof) {
+ // Ignore empty lines.
+ LOG(WARNING) << "inode2filename: got empty line";
+ }
+ continue;
+ }
+
+ LOG(DEBUG) << "inode2filename output-line: " << inode2filename_line;
+
+ std::optional<InodeResult> res = ParseFromLine(inode2filename_line);
+ if (!res) {
+ std::string error_msg = "Invalid output: ";
+ error_msg += inode2filename_line;
+ dest.on_error(
+ rxcpp::util::make_error_ptr(std::ios_base::failure(error_msg)));
+ return;
+ }
+ dest.on_next(*res);
+
+ ++actual_result_count;
+ }
+
+ LOG(DEBUG) << "inode2filename output-eof";
+
+ // Ensure that the # of inputs into the rx stream match the # of outputs.
+ // This is validating the post-condition of FindFilenamesFromInodes.
+ if (result_count && actual_result_count != *result_count) {
+ std::stringstream ss;
+ ss << "Invalid number of results, expected: " << *result_count;
+ ss << ", actual: " << actual_result_count;
+
+ dest.on_error(
+ rxcpp::util::make_error_ptr(std::ios_base::failure(ss.str())));
+ return;
+ }
+
+ CHECK(child > 0); // we are in the parent process, parse the IPC output of inode2filename
+ dest.on_completed();
+ }
+};
+
+rxcpp::observable<InodeResult>
+ OutOfProcessInodeResolver::FindFilenamesFromInodes(std::vector<Inode> inodes) const {
+ return rxcpp::observable<>::create<InodeResult>(
+ [self=std::static_pointer_cast<const OutOfProcessInodeResolver>(shared_from_this()),
+ inodes=std::move(inodes)](
+ rxcpp::subscriber<InodeResult> s) {
+ self->impl_->EmitFromCommandFind(s, self->GetDependencies(), inodes);
+ });
+}
+
+rxcpp::observable<InodeResult>
+ OutOfProcessInodeResolver::EmitAll() const {
+ auto self = std::static_pointer_cast<const OutOfProcessInodeResolver>(shared_from_this());
+ CHECK(self != nullptr);
+ CHECK(self->impl_ != nullptr);
+
+ return rxcpp::observable<>::create<InodeResult>(
+ [self](rxcpp::subscriber<InodeResult> s) {
+ CHECK(self != nullptr);
+ CHECK(self->impl_ != nullptr);
+ self->impl_->EmitFromCommandAll(s, self->GetDependencies());
+ });
+}
+
+OutOfProcessInodeResolver::OutOfProcessInodeResolver(InodeResolverDependencies dependencies)
+ : InodeResolver{std::move(dependencies)}, impl_{new Impl{}} {
+}
+
+OutOfProcessInodeResolver::~OutOfProcessInodeResolver() {
+ // std::unique_ptr requires complete types, but we hide the definition in the header.
+ delete impl_;
+}
+
+} // namespace iorap::inode2filename
diff --git a/src/inode2filename/out_of_process_inode_resolver.h b/src/inode2filename/out_of_process_inode_resolver.h
new file mode 100644
index 0000000..c9f4291
--- /dev/null
+++ b/src/inode2filename/out_of_process_inode_resolver.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef IORAP_SRC_INODE2FILENAME_OUT_OF_PROCESS_INDOE_RESOLVER_H_
+#define IORAP_SRC_INODE2FILENAME_OUT_OF_PROCESS_INDOE_RESOLVER_H_
+
+#include "common/expected.h"
+#include "inode2filename/inode_resolver.h"
+
+namespace iorap::inode2filename {
+
+// Create an InodeResolver that fork+exec+pipes into the binary 'iorap.inode2filename'
+// and transmits the results back via an IPC mechanism.
+//
+// This is instantiated by InodeResolver::Create + ProcessMode::kOutOfProcessIpc
+class OutOfProcessInodeResolver : public InodeResolver {
+ public:
+ virtual rxcpp::observable<InodeResult>
+ FindFilenamesFromInodes(std::vector<Inode> inodes) const override;
+
+ virtual rxcpp::observable<InodeResult>
+ EmitAll() const override;
+
+ OutOfProcessInodeResolver(InodeResolverDependencies dependencies);
+ ~OutOfProcessInodeResolver();
+ private:
+ struct Impl;
+ Impl* impl_;
+};
+
+}
+
+#endif // IORAP_SRC_INODE2FILENAME_OUT_OF_PROCESS_INDOE_RESOLVER_H_
diff --git a/src/inode2filename/search_directories.cc b/src/inode2filename/search_directories.cc
index 2ec09d9..1d31671 100644
--- a/src/inode2filename/search_directories.cc
+++ b/src/inode2filename/search_directories.cc
@@ -53,11 +53,45 @@ using android::base::StringPrintf; // NOLINT
namespace iorap::inode2filename {
+#define DEBUG_INODE_SET 0
+
// A multimap of 'ino_t -> List[Inode]' (where the value Inodes have the same ino_t as the key).
//
// A flat list of Inodes is turned into the above map, then keys can be removed one at a time
// until the InodeSet eventually becomes empty.
struct InodeSet {
+
+ InodeSet() = default;
+#if DEBUG_INODE_SET
+ InodeSet(const InodeSet& other) {
+ LOG(INFO) << "InodeSet-copyctor";
+ set_ = other.set_;
+ }
+
+ InodeSet(InodeSet&& other) {
+ LOG(INFO) << "InodeSet-movector";
+ set_ = std::move(other.set_);
+ }
+
+ InodeSet& operator=(const InodeSet& other) {
+ LOG(INFO) << "InodeSet-opassign-copy";
+ set_ = other.set_;
+ return *this;
+ }
+
+ InodeSet& operator=(InodeSet&& other) {
+ LOG(INFO) << "InodeSet-opassign-move";
+ set_ = std::move(other.set_);
+ return *this;
+ }
+#else
+ InodeSet(InodeSet&& other) = default;
+ InodeSet& operator=(InodeSet&& other) = default;
+ // Copying InodeSet can be very expensive, refuse to even allow compiling such code.
+ InodeSet(const InodeSet& other) = delete;
+ InodeSet& operator=(const InodeSet& other) = delete;
+#endif
+
struct ValueRange {
auto/*Iterable<Inode>*/ begin() {
return begin_;
@@ -149,7 +183,7 @@ struct InodeSet {
});
if (found != inode_list.end()) {
- const Inode& inode = found->second;
+ Inode inode = found->second;
LOG(VERBOSE) << "InodeSet:FindAndRemoveInodeInList *success* inode+device " << inode;
DCHECK(found->second.inode == stat_buf.st_ino);
// Erase the inode from the list. This is important.
@@ -160,6 +194,33 @@ struct InodeSet {
return std::nullopt;
}
+ // Match all fields of an Inode against another Inode.
+ //
+ // The returned Inode (if any) is removed from the InodeSet; it will not be returned by
+ // FindInodeList in future calls.
+ std::optional<Inode> FindAndRemoveInodeInList(ValueRange inode_list,
+ const Inode& inode) {
+ LOG(VERBOSE) << "FindAndRemoveInodeInList " << inode_list << ", "
+ << inode << "}";
+
+ auto /*iterator*/ found = std::find_if(inode_list.begin(),
+ inode_list.end(),
+ [&](const std::pair<ino_t, Inode>& pair) {
+ return inode == pair.second;
+ });
+
+ if (found != inode_list.end()) {
+ Inode inode = found->second;
+ LOG(VERBOSE) << "InodeSet:FindAndRemoveInodeInList *success* inode+device " << inode;
+ DCHECK_EQ(found->second, inode);
+ // Erase the inode from the list. This is important.
+ set_.erase(found);
+ return inode;
+ }
+
+ return std::nullopt;
+ }
+
// TODO: equality and string operators for testing/logging.
private:
// Explanation: readdir returns a 'file' -> 'ino_t inode' mapping.
@@ -641,7 +702,8 @@ struct StatError {
};
std::ostream& operator<<(std::ostream& os, const StatError& e) {
- os << "StatError{" << e.err_no << "," << e.path_name << "}";
+ os << "StatError{" << e.err_no << "," << e.path_name
+ << ": " << strerror(e.err_no) << "}";
return os;
}
@@ -685,6 +747,12 @@ struct SearchState {
// The InodeSet removes any matching 'Inode'.
std::optional<SearchMatch> match;
+ SearchState() = default;
+ SearchState(SearchState&& other) = default;
+
+ // Do not copy this because copying InodeSet is excruciatingly slow.
+ SearchState(const SearchState& other) = delete;
+
// TODO: make sure this doesn't copy [inodes], as that would be unnecessarily expensive.
};
@@ -727,18 +795,19 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
// DirectoryEntry is missing the dev_t portion, so we may need to call scan(2) again
// to confirm the dev_t. We skip calling scan(2) when the ino_t does not match.
// InodeSet lets us optimally avoid calling scan(2).
- SearchState initial;
- initial.inode_set = InodeSet::OfList(inode_search_list);
+ std::shared_ptr<SearchState> initial = std::make_shared<SearchState>();
+ initial->inode_set = InodeSet::OfList(inode_search_list);
auto/*[observable<SearchState>,Connectable]*/ search_state_results = find_all_subdir_entries.scan(
std::move(initial),
- [system_call=system_call](SearchState search_state, const DirectoryEntry& dir_entry) {
+ [system_call=system_call](std::shared_ptr<SearchState> search_state,
+ const DirectoryEntry& dir_entry) {
LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#Scan "
- << dir_entry << ", state: " << search_state;
+ << dir_entry << ", state: " << *search_state;
- search_state.match = std::nullopt;
+ search_state->match = std::nullopt;
- InodeSet* inodes = &search_state.inode_set;
+ InodeSet* inodes = &search_state->inode_set;
// Find all the possible inodes across different devices.
InodeSet::ValueRange inode_list = inodes->FindInodeList(dir_entry.d_ino);
@@ -754,23 +823,23 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
std::optional<Inode> inode = inodes->FindAndRemoveInodeInList(inode_list, stat_buf);
if (inode) {
- search_state.match = SearchMatch{inode.value(), dir_entry.filename};
+ search_state->match = SearchMatch{inode.value(), dir_entry.filename};
}
});
- return search_state; // implicit move.
+ return search_state;
}
// Avoid exhausting a potentially 'infinite' stream of files by terminating as soon
// as we find every single inode we care about.
- ).take_while([](const SearchState& state) {
+ ).take_while([](std::shared_ptr<SearchState> state) {
// Also emit the last item that caused the search set to go empty.
- bool cond = !state.inode_set.Empty() || state.match;
+ bool cond = !state->inode_set.Empty() || state->match;
if (WOULD_LOG(VERBOSE)) {
static int kCounter = 0;
LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#take_while (" << kCounter++ <<
",is_empty:"
- << state.inode_set.Empty() << ", match:" << state.match.has_value();
+ << state->inode_set.Empty() << ", match:" << state->match.has_value();
}
// Minor O(1) implementation inefficiency:
// (Too minor to fix but it can be strange if looking at the logs or readdir traces).
@@ -781,8 +850,8 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
// This means that for cond to go to false, we would've read one extra item and then discarded
// it. If that item was the first child of a directory, that means we essentially did
// one redundant pass of doing a readdir.
- //
// In other words if the search set goes to empty while the current item is a directory,
+ //
// it will definitely readdir on it at least once as we try to get the first child in
// OnTreeTraversal.
//
@@ -792,7 +861,7 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
if (!cond) {
LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#take_while "
- << "should now terminate for " << state;
+ << "should now terminate for " << *state;
}
return cond;
@@ -813,8 +882,10 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
// The 'matched_inode_values' only touches the match.
// Either stream can 'std::move' from those fields because they don't move each other's fields.
auto/*observable<InodeResult>*/ matched_inode_values = search_state_results
- .filter([](const SearchState& search_state) { return search_state.match.has_value(); })
- .map([](SearchState& search_state) { return std::move(search_state.match.value()); })
+ .filter([](std::shared_ptr<SearchState> search_state) {
+ return search_state->match.has_value(); })
+ .map([](std::shared_ptr<SearchState> search_state) {
+ return std::move(search_state->match.value()); })
// observable<SearchMatch>
.map([](SearchMatch search_match) {
return InodeResult::makeSuccess(search_match.inode, std::move(search_match.filename));
@@ -823,10 +894,10 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
auto/*observable<?>*/ unmatched_inode_values = search_state_results
// The 'last' SearchState is the one that contains all the remaining inodes.
.take_last(1) // observable<SearchState>
- .flat_map([](const SearchState& search_state) {
+ .flat_map([](std::shared_ptr<SearchState> search_state) {
LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#unmatched -- flat_map";
// Aside: Could've used a move here if the inodes weren't so lightweight already.
- return search_state.inode_set.IterateValues(); })
+ return search_state->inode_set.IterateValues(); })
// observable<Inode>
.map([](const Inode& inode) {
LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#unmatched -- map";
@@ -848,11 +919,10 @@ auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInode
return std::make_pair(all_inode_results, search_state_results);
}
-
rxcpp::observable<InodeResult> SearchDirectories::FindFilenamesFromInodes(
std::vector<std::string> root_directories,
std::vector<Inode> inode_list,
- SearchMode mode) {
+ SearchMode mode) const {
DCHECK(mode == SearchMode::kInProcessDirect) << " other modes not implemented yet";
auto/*observable[2]*/ [inode_results, connectable] = SearchDirectoriesForMatchingInodes(
@@ -860,7 +930,7 @@ rxcpp::observable<InodeResult> SearchDirectories::FindFilenamesFromInodes(
std::move(inode_list),
system_call_);
- return inode_results;
+ return inode_results.ref_count(connectable);
}
// I think we could avoid this with auto_connect, which rxcpp doesn't seem to have.
@@ -898,7 +968,7 @@ std::pair<rxcpp::observable<InodeResult>, std::unique_ptr<SearchDirectories::RxA
SearchDirectories::FindFilenamesFromInodesPair(
std::vector<std::string> root_directories,
std::vector<Inode> inode_list,
- SearchMode mode) {
+ SearchMode mode) const {
DCHECK(mode == SearchMode::kInProcessDirect) << " other modes not implemented yet";
auto/*observable[2]*/ [inode_results, connectable] = SearchDirectoriesForMatchingInodes(
@@ -912,4 +982,385 @@ std::pair<rxcpp::observable<InodeResult>, std::unique_ptr<SearchDirectories::RxA
return {inode_results, std::move(connectable_ptr)};
}
+rxcpp::observable<InodeResult>
+ SearchDirectories::FindFilenamesFromInodes(std::vector<std::string> root_directories,
+ rxcpp::observable<Inode> inodes,
+ SearchMode mode) const {
+
+ // It's inefficient to search for inodes until the full search list is available,
+ // so first reduce to a vector so we can access all the inodes simultaneously.
+ return inodes.reduce(std::vector<Inode>{},
+ [](std::vector<Inode> vec, Inode inode) {
+ vec.push_back(inode);
+ return vec;
+ },
+ [](std::vector<Inode> v){
+ return v; // TODO: use an identity function
+ })
+ .flat_map([root_directories=std::move(root_directories), mode, self=*this]
+ (std::vector<Inode> vec) {
+ // All borrowed values (e.g. SystemCall) must outlive the observable.
+ return self.FindFilenamesFromInodes(root_directories, vec, mode);
+ }
+ );
+}
+
+auto/*[observable<InodeResult>]*/ EmitAllInodesFromDirectories(
+ std::vector<std::string> root_dirs,
+ borrowed<SystemCall*> system_call) {
+
+ // Create a (lazy) observable that will emit each DirectoryEntry that is a recursive subchild
+ // of root_dirs. Emission will be stopped when its unsubscribed from.
+ //
+ // This is done by calling readdir(3) lazily.
+ auto/*obs<DirectoryEntry>*/ find_all_subdir_entries = ([&]() {
+ DirectoryEntry sentinel = DirectoryEntry::CreateSentinel(std::move(root_dirs));
+ auto/*obs<DirectoryEntryResult*/ results = sentinel.GetSubTreePreOrderEntries(system_call);
+
+ // Drop any errors by logging them to logcat. "Unwrap" the expected into the underlying data.
+ auto/*obs<DirectoryEntry*>*/ expected_drop_errors = MapExpectedOrLogError(std::move(results));
+ return expected_drop_errors;
+ })();
+
+ // Fill in -1 for the dev_t since readdir only returns the ino_t.
+ // The caller of this function is expected to call stat(2) later on to fill in
+ // the full data.
+ return find_all_subdir_entries.map([](DirectoryEntry e) {
+ return InodeResult::makeSuccess(Inode::FromDeviceAndInode(-1, e.d_ino), std::move(e.filename));
+ });
+}
+
+rxcpp::observable<InodeResult>
+ SearchDirectories::ListAllFilenames(std::vector<std::string> root_directories) const {
+ // TODO: refactor implementation into DiskScanDataSource.
+ return EmitAllInodesFromDirectories(std::move(root_directories),
+ /*borrowed*/system_call_);
+}
+
+struct FilterState {
+ // Emit 'match' Inodes corresponding to the ones here.
+ InodeSet inode_set;
+
+ // An inode matching one of the ones in inode_set was discovered in the most-recently
+ // emitted SearchState.
+ //
+ // The InodeSet removes any matching 'Inode'.
+ std::optional<InodeResult> match;
+
+ FilterState() = default;
+ FilterState(FilterState&& other) = default;
+
+ // Copying the InodeSet is expensive, so forbid any copies.
+ FilterState(const FilterState& other) = delete;
+};
+
+std::ostream& operator<<(std::ostream& os, const FilterState& s) {
+ os << "FilterState{match:";
+ // Print the 'match' first. The InodeSet could be very large so it could be truncated in logs.
+ if (s.match) {
+ os << s.match.value();
+ } else {
+ os << "(none)";
+ }
+ os << ", inode_set:" << s.inode_set << "}";
+ return os;
+}
+
+rxcpp::observable<InodeResult> SearchDirectories::FilterFilenamesForSpecificInodes(
+ rxcpp::observable<InodeResult> all_inodes,
+ std::vector<Inode> inode_list,
+ bool missing_device_number, // missing dev_t portion?
+ bool needs_verification) const {
+ // TODO: refactor into InodeResolver
+
+ borrowed<SystemCall*> system_call = system_call_;
+
+ // InodeResult may be missing the dev_t portion, so we may need to call scan(2) again
+ // to confirm the dev_t. We skip calling scan(2) when the ino_t does not match.
+ // InodeSet lets us optimally avoid calling scan(2).
+ std::shared_ptr<FilterState> initial = std::make_shared<FilterState>();
+ initial->inode_set = InodeSet::OfList(inode_list);
+
+ auto/*[observable<FilterState>,Connectable]*/ filter_state_results = all_inodes.scan(
+ std::move(initial),
+ [system_call, missing_device_number]
+ (std::shared_ptr<FilterState> filter_state, InodeResult inode_result) {
+ LOG(VERBOSE) << "FilterFilenamesForSpecificInodes#Scan "
+ << inode_result << ", state: " << *filter_state;
+
+ filter_state->match = std::nullopt;
+
+ InodeSet* inodes = &filter_state->inode_set;
+
+ // Find all the possible (dev_t, ino_t) potential needles given an ino_t in the haystack.
+ InodeSet::ValueRange inode_list = inodes->FindInodeList(inode_result.inode.inode);
+
+ // This inode result doesn't correspond to any inodes we are searching for.
+ if (!inode_list) {
+ // Drop the result and keep going.
+ return filter_state;
+ }
+
+ if (missing_device_number) {
+ // Need to fill in dev_t by calling stat(2).
+ VisitValueOrLogError(std::move(inode_result.data), [&](std::string filename) {
+ StatResult maybe_stat = Stat(filename, system_call);
+ VisitValueOrLogError(maybe_stat, [&](const struct stat& stat_buf) {
+ // Try to match the specific inode. Usually this will not result in a match (nullopt).
+ std::optional<Inode> inode = inodes->FindAndRemoveInodeInList(inode_list, stat_buf);
+
+ if (inode) {
+ filter_state->match = InodeResult::makeSuccess(inode.value(), std::move(filename));
+ }
+ });
+
+ // Note: stat errors are logged here to make the error closer to the occurrence.
+ // In theory, we could just return it as an InodeResult but then the error would
+ // just get logged elsewhere.
+ });
+ } else {
+ // Trust the dev_t in InodeResult is valid. Later passes can verify it.
+
+ // Try to match the specific inode. Usually this will not result in a match (nullopt).
+ std::optional<Inode> inode =
+ inodes->FindAndRemoveInodeInList(inode_list, inode_result.inode);
+
+ if (inode) {
+ filter_state->match = inode_result;
+ }
+
+ // Note that the InodeResult doesn't necessarily need to have a valid filename here.
+ // If the earlier pass returned an error-ed result, this will forward the error code.
+ }
+
+ return filter_state;
+ }
+ // Avoid exhausting a potentially 'infinite' stream of files by terminating as soon
+ // as we find every single inode we care about.
+ ).take_while([](std::shared_ptr<FilterState> state) {
+ // Also emit the last item that caused the search set to go empty.
+ bool cond = !state->inode_set.Empty() || state->match;
+
+ if (WOULD_LOG(VERBOSE)) {
+ static int kCounter = 0;
+ LOG(VERBOSE) << "FilterFilenamesForSpecificInodes#take_while (" << kCounter++ <<
+ ",is_empty:"
+ << state->inode_set.Empty() << ", match:" << state->match.has_value();
+ }
+ // Minor O(1) implementation inefficiency:
+ // (Too minor to fix but it can be strange if looking at the logs or readdir traces).
+ //
+ // Note, because we return 'true' after the search set went empty,
+ // the overall stream graph still pulls from filter_state_results exactly once more:
+ //
+ // This means that for cond to go to false, we would've read one extra item and then discarded
+ // it. If that item was the first child of a directory, that means we essentially did
+ // one redundant pass of doing a readdir.
+ // In other words if the search set goes to empty while the current item is a directory,
+ //
+ // it will definitely readdir on it at least once as we try to get the first child in
+ // OnTreeTraversal.
+ //
+ // This could be fixed with a 'take_until(Predicate)' operator which doesn't discard
+ // the last item when the condition becomes false. However rxcpp seems to lack this operator,
+ // whereas RxJava has it.
+
+ if (!cond) {
+ LOG(VERBOSE) << "FilterFilenamesForSpecificInodes#take_while "
+ << "should now terminate for " << *state;
+ }
+
+ return cond;
+ }).publish();
+ // The publish here is mandatory. The stream is consumed twice (once by matched and once by
+ // unmatched streams). Without the publish, once all items from 'matched' were consumed it would
+ // start another instance of 'filter_state_results' (i.e. it appears as if the search
+ // is restarted).
+ //
+ // By using 'publish', the filter_state_results is effectively shared by both downstream nodes.
+ // Note that this also requires the subscriber to additionally call #connect on the above stream,
+ // otherwise no work will happen.
+
+ // Lifetime notes:
+ //
+ // The the 'FilterState' is emitted into both below streams simultaneously.
+ // The 'unmatched_inode_values' only touches the inode_set.
+ // The 'matched_inode_values' only touches the match.
+ // Either stream can 'std::move' from those fields because they don't move each other's fields.
+ auto/*observable<InodeResult>*/ matched_inode_values = filter_state_results
+ .filter([](std::shared_ptr<FilterState> filter_state) {
+ return filter_state->match.has_value(); })
+ .map([](std::shared_ptr<FilterState> filter_state) {
+ return std::move(filter_state->match.value()); });
+ // observable<InodeResult>
+
+ auto/*observable<?>*/ unmatched_inode_values = filter_state_results
+ // The 'last' FilterState is the one that contains all the remaining inodes.
+ .take_last(1) // observable<FilterState>
+ .flat_map([](std::shared_ptr<FilterState> filter_state) {
+ LOG(VERBOSE) << "FilterFilenamesForSpecificInodes#unmatched -- flat_map";
+ // Aside: Could've used a move here if the inodes weren't so lightweight already.
+ return filter_state->inode_set.IterateValues(); })
+ // observable<Inode>
+ .map([](const Inode& inode) {
+ LOG(VERBOSE) << "FilterFilenamesForSpecificInodes#unmatched -- map";
+ return InodeResult::makeFailure(inode, InodeResult::kCouldNotFindFilename);
+ });
+ // observable<InodeResult>
+
+ // The matched and unmatched InodeResults are emitted together.
+ // Use merge, not concat, because we need both observables to be subscribed to simultaneously.
+
+ auto/*observable<InodeResult*/ all_inode_results =
+ matched_inode_values.merge(unmatched_inode_values);
+
+ // Verify the inode results by calling stat(2).
+ // Unverified results are turned into an error.
+
+ auto/*observable<InodeResult>*/ verified_inode_results =
+ all_inode_results.map([needs_verification, system_call](InodeResult result) {
+ if (!needs_verification || !result) {
+ // Skip verification if requested, or if the result didn't have a filename.
+ return result;
+ }
+
+ const std::string& filename = result.data.value();
+ StatResult maybe_stat = Stat(filename, system_call);
+
+ if (maybe_stat)
+ {
+ if (result.inode == Inode::FromDeviceAndInode(maybe_stat->st_dev, maybe_stat->st_ino)) {
+ return result;
+ } else {
+ LOG(WARNING)
+ << "FilterFilenamesForSpecificInodes#verified fail out-of-date inode: " << result;
+ return InodeResult::makeFailure(result.inode, InodeResult::kVerificationFailed);
+ }
+ } else {
+ // Forward stat errors directly, as it could be a missing security rule,
+ // but turn -ENOENT into casual verification errors.
+ const StatError& err = maybe_stat.error();
+ int error_code = err.err_no;
+ if (err.err_no == ENOENT) {
+ error_code = InodeResult::kVerificationFailed;
+
+ // TODO: Don't LOG(WARNING) here because this could be very common if we
+ // access the data much much later after the initial results were read in.
+ LOG(WARNING)
+ << "FilterFilenamesForSpecificInodes#verified fail out-of-date filename: " << result;
+ } else {
+ LOG(ERROR)
+ << "FilterFilenamesForSpecificInodes#verified stat(2) failure: " << err;
+ }
+
+ return InodeResult::makeFailure(result.inode, error_code);
+ }
+ });
+
+ // Now that all mid-stream observables have been connected, turn the Connectable observable
+ // into a regular observable.
+ return verified_inode_results.ref_count(filter_state_results);
+}
+
+rxcpp::observable<InodeResult> SearchDirectories::EmitAllFilenames(
+ rxcpp::observable<InodeResult> all_inodes,
+ bool missing_device_number, // missing dev_t portion?
+ bool needs_verification) const {
+ // TODO: refactor into InodeResolver
+
+ borrowed<SystemCall*> system_call = system_call_;
+
+ // InodeResult may be missing the dev_t portion, so we may need to call scan(2) again
+ // to confirm the dev_t.
+
+ using EmitAllState = std::optional<InodeResult>;
+
+ auto/*[observable<FilterState>,Connectable]*/ all_inode_results = all_inodes.map(
+ [system_call, missing_device_number](InodeResult inode_result) {
+ LOG(VERBOSE) << "EmitAllFilenames#map "
+ << inode_result;
+
+ // Could fail if the device number is missing _and_ stat(2) fails.
+ EmitAllState match = std::nullopt;
+
+ if (missing_device_number) {
+ // Need to fill in dev_t by calling stat(2).
+ VisitValueOrLogError(std::move(inode_result.data), [&](std::string filename) {
+ StatResult maybe_stat = Stat(filename, system_call);
+ VisitValueOrLogError(maybe_stat, [&](const struct stat& stat_buf) {
+ Inode inode = Inode::FromDeviceAndInode(stat_buf.st_dev, stat_buf.st_ino);
+ match = InodeResult::makeSuccess(inode, std::move(filename));
+ });
+
+ // Note: stat errors are logged here to make the error closer to the occurrence.
+ // In theory, we could just return it as an InodeResult but then the error would
+ // just get logged elsewhere.
+ });
+ } else {
+ // Trust the dev_t in InodeResult is valid. Later passes can verify it.
+ match = std::move(inode_result);
+
+ // Note that the InodeResult doesn't necessarily need to have a valid filename here.
+ // If the earlier pass returned an error-ed result, this will forward the error code.
+ }
+
+ return match; // implicit move.
+ }
+ );
+
+ auto/*observable<InodeResult>*/ matched_inode_values = all_inode_results
+ .filter([](const EmitAllState& filter_state) { return filter_state.has_value(); })
+ .map([](EmitAllState& filter_state) { return std::move(filter_state.value()); });
+ // observable<InodeResult>
+
+ // Verify the inode results by calling stat(2).
+ // Unverified results are turned into an error.
+
+ auto/*observable<InodeResult>*/ verified_inode_results =
+ matched_inode_values.map([needs_verification, system_call](InodeResult result) {
+ if (!needs_verification || !result) {
+ // Skip verification if requested, or if the result didn't have a filename.
+ return result;
+ }
+
+ const std::string& filename = result.data.value();
+ StatResult maybe_stat = Stat(filename, system_call);
+
+ if (maybe_stat)
+ {
+ if (result.inode == Inode::FromDeviceAndInode(maybe_stat->st_dev, maybe_stat->st_ino)) {
+ return result;
+ } else {
+ LOG(WARNING)
+ << "EmitAllFilenames#verified fail out-of-date inode: " << result;
+ return InodeResult::makeFailure(result.inode, InodeResult::kVerificationFailed);
+ }
+ } else {
+ // Forward stat errors directly, as it could be a missing security rule,
+ // but turn -ENOENT into casual verification errors.
+ const StatError& err = maybe_stat.error();
+ int error_code = err.err_no;
+ if (err.err_no == ENOENT) {
+ error_code = InodeResult::kVerificationFailed;
+
+ // TODO: Don't LOG(WARNING) here because this could be very common if we
+ // access the data much much later after the initial results were read in.
+ LOG(WARNING)
+ << "EmitAllFilenames#verified fail out-of-date filename: " << result;
+ } else {
+ LOG(ERROR)
+ << "EmitAllFilenames#verified stat(2) failure: " << err;
+ }
+
+ return InodeResult::makeFailure(result.inode, error_code);
+ }
+ });
+
+ // TODO: refactor this function some more with the Find(inode_set) equivalent.
+
+ // Now that all mid-stream observables have been connected, turn the Connectable observable
+ // into a regular observable.
+ return verified_inode_results;
+}
+
} // namespace iorap::inode2filename
diff --git a/src/inode2filename/search_directories.h b/src/inode2filename/search_directories.h
index 8156574..0b4af78 100644
--- a/src/inode2filename/search_directories.h
+++ b/src/inode2filename/search_directories.h
@@ -15,52 +15,17 @@
#ifndef IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_
#define IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_
-#include "common/expected.h"
#include "inode2filename/inode.h"
#include "inode2filename/system_call.h"
+#include "inode2filename/inode_resolver.h"
#include <fruit/fruit.h>
#include <rxcpp/rx.hpp>
namespace iorap::inode2filename {
-// Tuple of (Inode -> (Filename|Errno))
-struct InodeResult {
- // We set this error when all root directories have been searched and
- // yet we still could not find a corresponding filename for the inode under search.
- static constexpr int kCouldNotFindFilename = -ENOENT;
-
- Inode inode;
- // Value: Contains the filename (with a root directory as a prefix).
- // Error: Contains the errno, usually -ENOENT or perhaps a security error.
- iorap::expected<std::string /*filename*/, int /*errno*/> data;
-
- static InodeResult makeSuccess(Inode inode, std::string filename) {
- return InodeResult{inode, std::move(filename)};
- }
-
- static InodeResult makeFailure(Inode inode, int err_no) {
- return InodeResult{inode, iorap::unexpected{err_no}};
- }
-
- constexpr operator bool() const {
- return data.has_value();
- }
-};
-
-enum class SearchMode {
- // Test modes:
- kInProcessDirect, // Execute the code directly.
- kInProcessIpc, // Execute code via IPC layer using multiple threads.
- // Shipping mode:
- kOutOfProcessIpc, // Execute code via fork+exec with IPC.
-
- // Note: in-process system-wide stat(2)/readdir/etc is blocked by selinux.
- // Attempting to call the test modes will fail with -EPERM.
- //
- // Use fork+exec mode in shipping configurations, which spawns inode2filename
- // as a separate command.
-};
+// TODO: rename.
+using SearchMode = ProcessMode;
struct SearchDirectories {
// Type-erased subset of rxcpp::connectable_observable<?>
@@ -100,7 +65,7 @@ struct SearchDirectories {
rxcpp::observable<InodeResult>
FindFilenamesFromInodes(std::vector<std::string> root_directories,
std::vector<Inode> inode_list,
- SearchMode mode);
+ SearchMode mode) const;
// Create a cold observable of inode results (a lazy stream) corresponding
// to the inode search list.
@@ -126,7 +91,37 @@ struct SearchDirectories {
std::pair<rxcpp::observable<InodeResult>, std::unique_ptr<RxAnyConnectable>>
FindFilenamesFromInodesPair(std::vector<std::string> root_directories,
std::vector<Inode> inode_list,
- SearchMode mode);
+ SearchMode mode) const;
+
+ // No items on the output stream will be emitted until 'inodes' completes.
+ //
+ // The current algorithm is a naive DFS, so if it began too early it would either
+ // miss the search items or require traversal restarts.
+ //
+ // See above for more details.
+ rxcpp::observable<InodeResult>
+ FindFilenamesFromInodes(std::vector<std::string> root_directories,
+ rxcpp::observable<Inode> inodes,
+ SearchMode mode) const;
+
+ rxcpp::observable<InodeResult>
+ ListAllFilenames(std::vector<std::string> root_directories) const;
+
+ rxcpp::observable<InodeResult> FilterFilenamesForSpecificInodes(
+ // haystack that will be subscribed to until all in inode_list are found.
+ rxcpp::observable<InodeResult> all_inodes,
+ // key list: traverse all_inodes until we emit all results from inode_list.
+ std::vector<Inode> inode_list,
+ // all_inodes have a missing device number: use stat(2) to fill it in.
+ bool missing_device_number,
+ bool needs_verification) const;
+
+ rxcpp::observable<InodeResult> EmitAllFilenames(
+ // haystack that will be subscribed to until all in inode_list are found.
+ rxcpp::observable<InodeResult> all_inodes,
+ // all_inodes have a missing device number: use stat(2) to fill it in.
+ bool missing_device_number,
+ bool needs_verification) const;
// Any borrowed parameters here can also be borrowed by the observables returned by the above
// member functions.
diff --git a/src/iorapd/main.cc b/src/iorapd/main.cc
index ab73fe5..8d806ba 100644
--- a/src/iorapd/main.cc
+++ b/src/iorapd/main.cc
@@ -16,6 +16,8 @@
#include "binder/iiorap_impl.h"
#include "common/debug.h"
+#include "common/loggers.h"
+#include "db/models.h"
#include "manager/event_manager.h"
#include <android-base/logging.h>
@@ -27,27 +29,6 @@
static constexpr const char* kServiceName = iorap::binder::IIorapImpl::getServiceName();
-// Log to both Stderr and Logd for convenience when running this from the command line.
-class StderrAndLogdLogger {
- public:
- explicit StderrAndLogdLogger(android::base::LogId default_log_id = android::base::MAIN)
- : logd_(default_log_id) {
- }
-
- void operator()(::android::base::LogId id,
- ::android::base::LogSeverity sev,
- const char* tag,
- const char* file,
- unsigned int line,
- const char* message) {
- logd_(id, sev, tag, file, line, message);
- StderrLogger(id, sev, tag, file, line, message);
- }
-
- private:
- ::android::base::LogdLogger logd_;
-};
-
int main(int /*argc*/, char** argv) {
if (android::base::GetBoolProperty("iorapd.log.verbose", iorap::kIsDebugBuild)) {
// Show verbose logs if the property is enabled or if we are a debug build.
@@ -55,23 +36,35 @@ int main(int /*argc*/, char** argv) {
}
// Logs go to system logcat.
- android::base::InitLogging(argv, StderrAndLogdLogger{android::base::SYSTEM});
+ android::base::InitLogging(argv, iorap::common::StderrAndLogdLogger{android::base::SYSTEM});
+ LOG(INFO) << kServiceName << " (the prefetchening) firing up";
{
- android::ScopedTrace trace_main{ATRACE_TAG_PACKAGE_MANAGER, "main"};
- LOG(INFO) << kServiceName << " (the prefetchening) firing up";
+ android::ScopedTrace trace_db_init{ATRACE_TAG_ACTIVITY_MANAGER, "IorapNativeService::db_init"};
+ iorap::db::SchemaModel db_schema =
+ iorap::db::SchemaModel::GetOrCreate(
+ android::base::GetProperty("iorapd.db.location",
+ "/data/misc/iorapd/sqlite.db"));
+ db_schema.MarkSingleton();
+ }
- android::ScopedTrace trace_start{ATRACE_TAG_PACKAGE_MANAGER, "IorapNativeService::start"};
+ std::shared_ptr<iorap::manager::EventManager> event_manager;
+ {
+ android::ScopedTrace trace_start{ATRACE_TAG_ACTIVITY_MANAGER, "IorapNativeService::start"};
// TODO: use fruit for this DI.
- auto /*std::shared_ptr<EventManager>*/ event_manager =
+ event_manager =
iorap::manager::EventManager::Create();
- if (!iorap::binder::IIorapImpl::Start(std::move(event_manager))) {
+ if (!iorap::binder::IIorapImpl::Start(event_manager)) {
LOG(ERROR) << "Unable to start IorapNativeService";
exit(1);
}
}
+ // This must be logged after all other initialization has finished.
+ LOG(INFO) << kServiceName << " (the prefetchening) readied up";
+
+ event_manager->Join(); // TODO: shutdown somewhere?
// Block until something else shuts down the binder service.
android::IPCThreadState::self()->joinThreadPool();
LOG(INFO) << kServiceName << " shutting down";
diff --git a/src/maintenance/controller.cc b/src/maintenance/controller.cc
new file mode 100644
index 0000000..daaf08f
--- /dev/null
+++ b/src/maintenance/controller.cc
@@ -0,0 +1,620 @@
+// Copyright (C) 2019 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 "compiler/compiler.h"
+#include "maintenance/controller.h"
+
+#include "common/cmd_utils.h"
+#include "common/debug.h"
+#include "common/expected.h"
+#include "common/trace.h"
+
+#include "db/models.h"
+#include "inode2filename/inode.h"
+#include "inode2filename/search_directories.h"
+#include "prefetcher/read_ahead.h"
+
+#include <android-base/file.h>
+#include <utils/Printer.h>
+
+#include <chrono>
+#include <ctime>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <limits>
+#include <mutex>
+#include <optional>
+#include <vector>
+#include <string>
+#include <sys/wait.h>
+
+namespace iorap::maintenance {
+
+const constexpr int64_t kCompilerCheckIntervalMs = 10;
+static constexpr size_t kMinTracesForCompilation = 1;
+
+struct LastJobInfo {
+ time_t last_run_ns_{0};
+ size_t activities_last_compiled_{0};
+};
+
+LastJobInfo last_job_info_;
+std::mutex last_job_info_mutex_;
+
+// Gets the path of output compiled trace.
+db::CompiledTraceFileModel CalculateNewestFilePath(
+ const std::string& package_name,
+ const std::string& activity_name,
+ int version) {
+ db::VersionedComponentName versioned_component_name{
+ package_name, activity_name, version};
+
+ db::CompiledTraceFileModel output_file =
+ db::CompiledTraceFileModel::CalculateNewestFilePath(versioned_component_name);
+
+ return output_file;
+}
+
+using ArgString = const char*;
+
+static constexpr const char kCommandFileName[] = "/system/bin/iorap.cmd.compiler";
+
+int Exec::Execve(const std::string& pathname,
+ std::vector<std::string>& argv_vec,
+ char *const envp[]) {
+ std::unique_ptr<ArgString[]> argv_ptr =
+ common::VecToArgv(kCommandFileName, argv_vec);
+
+ return execve(pathname.c_str(), (char**)argv_ptr.get(), envp);
+}
+
+pid_t Exec::Fork() {
+ return fork();
+}
+
+// Represents the parameters used when fork+exec compiler.
+struct CompilerForkParameters {
+ std::vector<std::string> input_pbs;
+ std::vector<uint64_t> timestamp_limit_ns;
+ std::string output_proto;
+ ControllerParameters controller_params;
+
+ CompilerForkParameters(const std::vector<compiler::CompilationInput>& perfetto_traces,
+ const std::string& output_proto,
+ ControllerParameters controller_params) :
+ output_proto(output_proto), controller_params(controller_params) {
+ for (compiler::CompilationInput perfetto_trace : perfetto_traces) {
+ input_pbs.push_back(perfetto_trace.filename);
+ timestamp_limit_ns.push_back(perfetto_trace.timestamp_limit_ns);
+ }
+ }
+};
+
+std::vector<std::string> MakeCompilerParams(const CompilerForkParameters& params) {
+ std::vector<std::string> argv;
+ ControllerParameters controller_params = params.controller_params;
+
+ common::AppendArgsRepeatedly(argv, params.input_pbs);
+ common::AppendArgsRepeatedly(argv, "--timestamp_limit_ns", params.timestamp_limit_ns);
+
+ if (controller_params.output_text) {
+ argv.push_back("--output-text");
+ }
+
+ common::AppendArgs(argv, "--output-proto", params.output_proto);
+
+ if (controller_params.inode_textcache) {
+ common::AppendArgs(argv, "--inode-textcache", *controller_params.inode_textcache);
+ }
+
+ if (controller_params.verbose) {
+ argv.push_back("--verbose");
+ }
+
+ return argv;
+}
+
+// Sets a watch dog for the given pid and kill it if timeout.
+std::thread SetTimeoutWatchDog(pid_t pid, int64_t timeout_ms, std::atomic<bool>& cancel_watchdog) {
+ std::thread watchdog_thread{[pid, timeout_ms, &cancel_watchdog]() {
+ std::chrono::time_point start = std::chrono::system_clock::now();
+ std::chrono::milliseconds timeout(timeout_ms);
+ while (!cancel_watchdog) {
+ int status = kill(pid, 0);
+ if (status != 0) {
+ LOG(DEBUG) << "Process (" << pid << ") doesn't exist now.";
+ break;
+ }
+ std::chrono::time_point cur = std::chrono::system_clock::now();
+ if (cur - start > timeout) {
+ LOG(INFO) << "Process (" << pid << ") is timeout!";
+ LOG(INFO) << "start time: "
+ << std::chrono::system_clock::to_time_t(start)
+ << " end time: "
+ << std::chrono::system_clock::to_time_t(cur)
+ << " timeout: "
+ << timeout_ms;
+ kill(pid, SIGKILL);
+ break;
+ }
+ usleep(kCompilerCheckIntervalMs * 1000);
+ }
+ }};
+
+ return watchdog_thread;
+}
+
+bool StartViaFork(const CompilerForkParameters& params) {
+ const ControllerParameters& controller_params = params.controller_params;
+ pid_t child = controller_params.exec->Fork();
+
+ if (child == -1) {
+ LOG(FATAL) << "Failed to fork a process for compilation";
+ } else if (child > 0) { // we are the caller of this function
+ LOG(DEBUG) << "forked into a process for compilation , pid = " << child;
+
+ int64_t compiler_timeout_ms =
+ android::base::GetIntProperty("iorapd.maintenance.compiler_timeout_ms",
+ /*default*/ 10 * 60 * 1000); // 10 min
+ std::atomic<bool> cancel_watchdog(false);
+ std::thread watchdog_thread = SetTimeoutWatchDog(child, compiler_timeout_ms, cancel_watchdog);
+ int wstatus;
+ waitpid(child, /*out*/&wstatus, /*options*/0);
+
+ // Terminate the thread after the compiler process is killed or done.
+ LOG(DEBUG) << "Terminate the watch dog thread.";
+ cancel_watchdog = true;
+ watchdog_thread.join();
+
+ if (!WIFEXITED(wstatus)) {
+ LOG(ERROR) << "Child terminated abnormally, status: " << WEXITSTATUS(wstatus);
+ return false;
+ }
+
+ int status = WEXITSTATUS(wstatus);
+ LOG(DEBUG) << "Child terminated, status: " << status;
+ if (status == 0) {
+ LOG(DEBUG) << "Iorap compilation succeeded";
+ return true;
+ } else {
+ LOG(ERROR) << "Iorap compilation failed";
+ return false;
+ }
+ } else {
+ // we are the child that was forked.
+ std::vector<std::string> argv_vec = MakeCompilerParams(params);
+ std::unique_ptr<ArgString[]> argv_ptr =
+ common::VecToArgv(kCommandFileName, argv_vec);
+
+ std::stringstream argv; // for debugging.
+ for (std::string arg : argv_vec) {
+ argv << arg << ' ';
+ }
+ LOG(DEBUG) << "fork+exec: " << kCommandFileName << " " << argv.str();
+
+ controller_params.exec->Execve(kCommandFileName,
+ argv_vec,
+ /*envp*/nullptr);
+ // This should never return.
+ }
+ return false;
+}
+
+// Gets the perfetto trace infos in the histories.
+std::vector<compiler::CompilationInput> GetPerfettoTraceInfo(
+ const db::DbHandle& db,
+ const std::vector<db::AppLaunchHistoryModel>& histories) {
+ std::vector<compiler::CompilationInput> perfetto_traces;
+
+ for(db::AppLaunchHistoryModel history : histories) {
+ // Get perfetto trace.
+ std::optional<db::RawTraceModel> raw_trace =
+ db::RawTraceModel::SelectByHistoryId(db, history.id);
+ if (!raw_trace) {
+ // This is normal: non-cold launches do not have traces.
+ continue;
+ }
+
+ uint64_t timestamp_limit = std::numeric_limits<uint64_t>::max();
+ // Get corresponding timestamp limit.
+ if (history.report_fully_drawn_ns) {
+ timestamp_limit = *history.report_fully_drawn_ns;
+ } else if (history.total_time_ns) {
+ timestamp_limit = *history.total_time_ns;
+ } else {
+ LOG(DEBUG) << " No timestamp exists. Using the max value.";
+ }
+ perfetto_traces.push_back({raw_trace->file_path, timestamp_limit});
+ }
+ return perfetto_traces;
+}
+
+// Helper struct for printing vector.
+template <class T>
+struct VectorPrinter {
+ std::vector<T>& values;
+};
+
+std::ostream& operator<<(std::ostream& os,
+ const struct compiler::CompilationInput& perfetto_trace) {
+ os << "file_path: " << perfetto_trace.filename << " "
+ << "timestamp_limit: " << perfetto_trace.timestamp_limit_ns;
+ return os;
+}
+
+template <class T>
+std::ostream& operator<<(std::ostream& os, const struct VectorPrinter<T>& printer) {
+ os << "[\n";
+ for (T i : printer.values) {
+ os << i << ",\n";
+ }
+ os << "]\n";
+ return os;
+}
+
+// Compiled the perfetto traces for an activity.
+bool CompileActivity(const db::DbHandle& db,
+ int package_id,
+ const std::string& package_name,
+ const std::string& activity_name,
+ int version,
+ const ControllerParameters& params) {
+ ScopedFormatTrace atrace_compile_package(ATRACE_TAG_PACKAGE_MANAGER,
+ "Compile activity %s",
+ activity_name.c_str());
+
+ LOG(DEBUG) << "CompileActivity: " << package_name << "/" << activity_name << "@" << version;
+
+ db::CompiledTraceFileModel output_file =
+ CalculateNewestFilePath(package_name, activity_name, version);
+
+ std::string file_path = output_file.FilePath();
+
+ if (!params.recompile) {
+ if (std::filesystem::exists(file_path)) {
+ LOG(DEBUG) << "compiled trace exists in " << file_path;
+
+ db::VersionedComponentName vcn{package_name, activity_name, version};
+ std::optional<db::PrefetchFileModel> prefetch_file =
+ db::PrefetchFileModel::SelectByVersionedComponentName(db, vcn);
+ if (prefetch_file) {
+ return true;
+ } else {
+ LOG(WARNING) << "Missing corresponding prefetch_file db row for " << vcn;
+ // let it go and compile again. we'll insert the prefetch_file at the bottom.
+ }
+ }
+ }
+
+ std::optional<db::ActivityModel> activity =
+ db::ActivityModel::SelectByNameAndPackageId(db, activity_name.c_str(), package_id);
+ if (!activity) {
+ LOG(ERROR) << "Cannot find activity for package_id: " << package_id
+ <<" activity_name: " <<activity_name;
+ return false;
+ }
+
+ int activity_id = activity->id;
+
+ std::vector<db::AppLaunchHistoryModel> histories =
+ db::AppLaunchHistoryModel::SelectActivityHistoryForCompile(db, activity_id);
+
+ {
+ std::vector<compiler::CompilationInput> perfetto_traces =
+ GetPerfettoTraceInfo(db, histories);
+
+ if (perfetto_traces.size() < params.min_traces) {
+ LOG(DEBUG) << "The number of perfetto traces is " << perfetto_traces.size()
+ <<", which is less than " << params.min_traces;
+ return false;
+ }
+
+ {
+ std::lock_guard<std::mutex> last_job_info_guard{last_job_info_mutex_};
+ last_job_info_.activities_last_compiled_++;
+ }
+
+ // Show the compilation config.
+ LOG(DEBUG) << "Try to compiled package_id: " << package_id
+ << " package_name: " << package_name
+ << " activity_name: " << activity_name
+ << " version: " << version
+ << " file_path: " << file_path
+ << " verbose: " << params.verbose
+ << " perfetto_traces: "
+ << VectorPrinter<compiler::CompilationInput>{perfetto_traces};
+ if (params.inode_textcache) {
+ LOG(DEBUG) << "inode_textcache: " << *params.inode_textcache;
+ }
+
+ CompilerForkParameters compiler_params{perfetto_traces, file_path, params};
+
+ if (!output_file.MkdirWithParents()) {
+ LOG(ERROR) << "Compile activity failed. Failed to mkdirs " << file_path;
+ return false;
+ }
+
+ ScopedFormatTrace atrace_compile_fork(ATRACE_TAG_PACKAGE_MANAGER,
+ "Fork+exec iorap.cmd.compiler",
+ activity_name.c_str());
+ if (!StartViaFork(compiler_params)) {
+ LOG(ERROR) << "Compilation failed for package_id:" << package_id
+ << " activity_name: " << activity_name;
+ return false;
+ }
+ }
+
+ std::optional<db::PrefetchFileModel> compiled_trace =
+ db::PrefetchFileModel::Insert(db, activity_id, file_path);
+ if (!compiled_trace) {
+ LOG(ERROR) << "Cannot insert compiled trace activity_id: " << activity_id
+ << " file_path: " << file_path;
+ return false;
+ }
+ return true;
+}
+
+// Compiled the perfetto traces for activities in an package.
+bool CompilePackage(const db::DbHandle& db,
+ const std::string& package_name,
+ int version,
+ const ControllerParameters& params) {
+ ScopedFormatTrace atrace_compile_package(ATRACE_TAG_PACKAGE_MANAGER,
+ "Compile package %s",
+ package_name.c_str());
+
+ std::optional<db::PackageModel> package =
+ db::PackageModel::SelectByNameAndVersion(db, package_name.c_str(), version);
+
+ if (!package) {
+ LOG(ERROR) << "Cannot find package for package_name: "
+ << package_name
+ << " and version "
+ << version;
+ return false;
+ }
+
+ std::vector<db::ActivityModel> activities =
+ db::ActivityModel::SelectByPackageId(db, package->id);
+
+ bool ret = true;
+ for (db::ActivityModel activity : activities) {
+ if (!CompileActivity(db, package->id, package->name, activity.name, version, params)) {
+ ret = false;
+ }
+ }
+ return ret;
+}
+
+// Compiled the perfetto traces for packages in a device.
+bool CompileAppsOnDevice(const db::DbHandle& db, const ControllerParameters& params) {
+ {
+ std::lock_guard<std::mutex> last_job_info_guard{last_job_info_mutex_};
+ last_job_info_.activities_last_compiled_ = 0;
+ }
+
+ std::vector<db::PackageModel> packages = db::PackageModel::SelectAll(db);
+ bool ret = true;
+ for (db::PackageModel package : packages) {
+ if (!CompilePackage(db, package.name, package.version, params)) {
+ ret = false;
+ }
+ }
+
+ {
+ std::lock_guard<std::mutex> last_job_info_guard{last_job_info_mutex_};
+ last_job_info_.last_run_ns_ = time(nullptr);
+ }
+
+ return ret;
+}
+
+// Compiled the perfetto traces for a single package in a device.
+bool CompileSingleAppOnDevice(const db::DbHandle& db,
+ const ControllerParameters& params,
+ const std::string& package_name) {
+ std::vector<db::PackageModel> packages = db::PackageModel::SelectByName(db, package_name.c_str());
+ bool ret = true;
+ for (db::PackageModel package : packages) {
+ if (!CompilePackage(db, package.name, package.version, params)) {
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+bool Compile(const std::string& db_path, const ControllerParameters& params) {
+ iorap::db::SchemaModel db_schema = db::SchemaModel::GetOrCreate(db_path);
+ db::DbHandle db{db_schema.db()};
+ return CompileAppsOnDevice(db, params);
+}
+
+bool Compile(const std::string& db_path,
+ const std::string& package_name,
+ int version,
+ const ControllerParameters& params) {
+ iorap::db::SchemaModel db_schema = db::SchemaModel::GetOrCreate(db_path);
+ db::DbHandle db{db_schema.db()};
+ return CompilePackage(db, package_name, version, params);
+}
+
+bool Compile(const std::string& db_path,
+ const std::string& package_name,
+ const std::string& activity_name,
+ int version,
+ const ControllerParameters& params) {
+ iorap::db::SchemaModel db_schema = db::SchemaModel::GetOrCreate(db_path);
+ db::DbHandle db{db_schema.db()};
+
+ std::optional<db::PackageModel> package =
+ db::PackageModel::SelectByNameAndVersion(db, package_name.c_str(), version);
+
+ if (!package) {
+ LOG(ERROR) << "Cannot find package with name "
+ << package_name
+ << " and version "
+ << version;
+ return false;
+ }
+ return CompileActivity(db, package->id, package_name, activity_name, version, params);
+}
+
+static std::string TimeToString(time_t the_time) {
+ tm tm_buf{};
+ tm* tm_ptr = localtime_r(&the_time, &tm_buf);
+
+ if (tm_ptr != nullptr) {
+ char time_buffer[256];
+ strftime(time_buffer, sizeof(time_buffer), "%a %b %d %H:%M:%S %Y", tm_ptr);
+ return std::string{time_buffer};
+ } else {
+ return std::string{"(nullptr)"};
+ }
+}
+
+static std::string GetTimestampForPrefetchFile(const db::PrefetchFileModel& prefetch_file) {
+ std::filesystem::path path{prefetch_file.file_path};
+
+ std::error_code ec{};
+ auto last_write_time = std::filesystem::last_write_time(path, /*out*/ec);
+ if (ec) {
+ return std::string("Failed to get last write time: ") + ec.message();
+ }
+
+ time_t time = decltype(last_write_time)::clock::to_time_t(last_write_time);
+
+ std::string time_str = TimeToString(time);
+ return time_str;
+}
+
+void DumpPackageActivity(const db::DbHandle& db,
+ ::android::Printer& printer,
+ const db::PackageModel& package,
+ const db::ActivityModel& activity) {
+ int package_id = package.id;
+ const std::string& package_name = package.name;
+ int package_version = package.version;
+ const std::string& activity_name = activity.name;
+ db::VersionedComponentName vcn{package_name, activity_name, package_version};
+
+ // com.google.Settings/com.google.Settings.ActivityMain@1234567890
+ printer.printFormatLine(" %s/%s@%d",
+ package_name.c_str(),
+ activity_name.c_str(),
+ package_version);
+
+ std::optional<db::PrefetchFileModel> prefetch_file =
+ db::PrefetchFileModel::SelectByVersionedComponentName(db, vcn);
+
+ std::vector<db::AppLaunchHistoryModel> histories =
+ db::AppLaunchHistoryModel::SelectActivityHistoryForCompile(db, activity.id);
+ std::vector<compiler::CompilationInput> perfetto_traces =
+ GetPerfettoTraceInfo(db, histories);
+
+ if (prefetch_file) {
+ bool exists_on_disk = std::filesystem::exists(prefetch_file->file_path);
+
+ std::optional<size_t> prefetch_byte_sum =
+ prefetcher::ReadAhead::PrefetchSizeInBytes(prefetch_file->file_path);
+
+ if (exists_on_disk) {
+ printer.printFormatLine(" Compiled Status: Usable compiled trace");
+ } else {
+ printer.printFormatLine(" Compiled Status: Prefetch file deleted from disk.");
+ }
+
+ if (prefetch_byte_sum) {
+ printer.printFormatLine(" Bytes to be prefetched: %zu", *prefetch_byte_sum);
+ } else {
+ printer.printFormatLine(" Bytes to be prefetched: (bad file path)" );
+ }
+
+ printer.printFormatLine(" Time compiled: %s",
+ GetTimestampForPrefetchFile(*prefetch_file).c_str());
+ printer.printFormatLine(" %s", prefetch_file->file_path.c_str());
+ } else {
+ size_t size = perfetto_traces.size();
+
+ if (size >= kMinTracesForCompilation) {
+ printer.printFormatLine(" Compiled Status: Raw traces pending compilation (%zu)",
+ perfetto_traces.size());
+ } else {
+ size_t remaining = kMinTracesForCompilation - size;
+ printer.printFormatLine(" Compiled Status: Need %zu more traces for compilation",
+ remaining);
+ }
+ }
+
+ printer.printFormatLine(" Raw traces:");
+ printer.printFormatLine(" Trace count: %zu", perfetto_traces.size());
+
+ for (compiler::CompilationInput& compilation_input : perfetto_traces) {
+ std::string& raw_trace_file_name = compilation_input.filename;
+
+ printer.printFormatLine(" %s", raw_trace_file_name.c_str());
+ }
+}
+
+void DumpPackage(const db::DbHandle& db,
+ ::android::Printer& printer,
+ db::PackageModel package) {
+ std::vector<db::ActivityModel> activities =
+ db::ActivityModel::SelectByPackageId(db, package.id);
+
+ for (db::ActivityModel& activity : activities) {
+ DumpPackageActivity(db, printer, package, activity);
+ }
+}
+
+void DumpAllPackages(const db::DbHandle& db, ::android::Printer& printer) {
+ printer.printLine("Package history in database:");
+
+ std::vector<db::PackageModel> packages = db::PackageModel::SelectAll(db);
+ for (db::PackageModel package : packages) {
+ DumpPackage(db, printer, package);
+ }
+
+ printer.printLine("");
+}
+
+void Dump(const db::DbHandle& db, ::android::Printer& printer) {
+ bool locked = last_job_info_mutex_.try_lock();
+
+ LastJobInfo info = last_job_info_;
+
+ printer.printFormatLine("Background job:");
+ if (!locked) {
+ printer.printLine(""""" (possible deadlock)");
+ }
+ if (info.last_run_ns_ != time_t{0}) {
+ std::string time_str = TimeToString(info.last_run_ns_);
+
+ printer.printFormatLine(" Last run at: %s", time_str.c_str());
+ } else {
+ printer.printFormatLine(" Last run at: (None)");
+ }
+ printer.printFormatLine(" Activities last compiled: %zu", info.activities_last_compiled_);
+
+ printer.printLine("");
+
+ if (locked) {
+ last_job_info_mutex_.unlock();
+ }
+
+ DumpAllPackages(db, printer);
+}
+
+} // namespace iorap::maintenance
diff --git a/src/maintenance/controller.h b/src/maintenance/controller.h
new file mode 100644
index 0000000..dbe6df9
--- /dev/null
+++ b/src/maintenance/controller.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2019 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 IORAP_SRC_MAINTENANCE_COMPILER_CONTROLLER_H_
+#define IORAP_SRC_MAINTENANCE_COMPILER_CONTROLLER_H_
+
+#include "db/file_models.h"
+#include "inode2filename/inode_resolver.h"
+
+#include <string>
+#include <vector>
+
+namespace android {
+class Printer;
+} // namespace android
+
+namespace iorap::maintenance {
+
+// Enabling mock for testing purpose.
+class IExec {
+ public:
+ virtual int Execve(const std::string& pathname,
+ std::vector<std::string>& argv_vec,
+ char *const envp[]) = 0;
+ virtual int Fork() = 0;
+ virtual ~IExec() = default;
+};
+
+class Exec : public IExec {
+ public:
+ virtual int Execve(const std::string& pathname,
+ std::vector<std::string>& argv_vec,
+ char *const envp[]);
+ virtual int Fork();
+};
+
+// Represents the parameters used for compilation controller.
+struct ControllerParameters {
+ bool output_text;
+ // The path of inode2filepath file.
+ std::optional<std::string> inode_textcache;
+ bool verbose;
+ bool recompile;
+ uint64_t min_traces;
+ std::shared_ptr<IExec> exec;
+
+ ControllerParameters(bool output_text,
+ std::optional<std::string> inode_textcache,
+ bool verbose,
+ bool recompile,
+ uint64_t min_traces,
+ std::shared_ptr<IExec> exec) :
+ output_text(output_text),
+ inode_textcache(inode_textcache),
+ verbose(verbose),
+ recompile(recompile),
+ min_traces(min_traces),
+ exec(exec) {
+ }
+};
+
+// Control the compilation of perfetto traces in the sqlite db.
+//
+// The strategy now is to compile all the existing perfetto traces for an activity
+// and skip ones if the number of perfetto traces is less than the min_traces.
+//
+// By default, the program doesn't replace the existing compiled trace, it just
+// return true. To force replace the existing compiled trace, set `force` to true.
+//
+// The timestamp limit of the each perfetto trace is determined by `report_fully_drawn_ns`
+// timestamp. If it doesn't exists, use `total_time_ns`. If neither of them exists,
+// use the max.
+
+// Compile all activities of all packages in the database.
+bool Compile(const std::string& db_path, const ControllerParameters& params);
+
+// Compile all activities in the package.
+// If the version is not given, an arbitrary package that has the same name is used.
+bool Compile(const std::string& db_path,
+ const std::string& package_name,
+ int version,
+ const ControllerParameters& params);
+
+// Compile trace for the activity.
+// If the version is not given, an arbitrary package has the same name is used.
+bool Compile(const std::string& db_path,
+ const std::string& package_name,
+ const std::string& activity_name,
+ int version,
+ const ControllerParameters& params);
+// Visible for testing.
+bool CompileAppsOnDevice(const db::DbHandle& db, const ControllerParameters& params);
+
+bool CompileSingleAppOnDevice(const db::DbHandle& db,
+ const ControllerParameters& params,
+ const std::string& package_name);
+
+void Dump(const db::DbHandle& db, ::android::Printer& printer);
+
+} // iorap::maintenance
+
+#endif // IORAP_SRC_MAINTENANCE_COMPILER_CONTROLLER_H_
diff --git a/src/maintenance/db_cleaner.cc b/src/maintenance/db_cleaner.cc
new file mode 100644
index 0000000..56eff70
--- /dev/null
+++ b/src/maintenance/db_cleaner.cc
@@ -0,0 +1,66 @@
+// 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 "maintenance/db_cleaner.h"
+
+#include <android-base/file.h>
+
+#include <cstdio>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "db/clean_up.h"
+#include "db/file_models.h"
+#include "db/models.h"
+
+namespace iorap::maintenance {
+
+// Enable foreign key restriction.
+const constexpr char* kForeignKeyOnSql = "PRAGMA foreign_keys = ON;";
+
+void CleanUpDatabase(const db::DbHandle& db,
+ std::shared_ptr<binder::PackageVersionMap> version_map) {
+ std::vector<db::PackageModel> packages = db::PackageModel::SelectAll(db);
+ // Enable cascade deletion.
+ if (!db::DbQueryBuilder::ExecuteOnce(db, kForeignKeyOnSql)) {
+ LOG(ERROR) << "Fail to turn on foreign key restraint!";
+ }
+
+ for (db::PackageModel package : packages) {
+ std::optional<int64_t> version = version_map->Find(package.name);
+ if (!version) {
+ LOG(DEBUG) << "Fail to find version for package " << package.name
+ << " with version " << package.version
+ << ". The package manager may be down.";
+ continue;
+ }
+ // Package is cleanup if it
+ // * is not in the version map, it may be uninstalled
+ // * has an different version with the latest one
+ if (*version != package.version) {
+ db::CleanUpFilesForPackage(db, package.id, package.name, package.version);
+ if (!package.Delete()) {
+ LOG(ERROR) << "Fail to delete package " << package.name
+ << " with version " << package.version;
+ }
+ }
+ }
+}
+
+} // namespace iorap::maintenance
diff --git a/src/maintenance/db_cleaner.h b/src/maintenance/db_cleaner.h
new file mode 100644
index 0000000..1168bd5
--- /dev/null
+++ b/src/maintenance/db_cleaner.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef IORAP_SRC_MAINTENANCE_VERSION_UPDATE_H_
+#define IORAP_SRC_MAINTENANCE_VERSION_UPDATE_H_
+
+#include <android/content/pm/IPackageManagerNative.h>
+
+#include <string>
+#include <vector>
+
+#include "binder/package_version_map.h"
+#include "db/file_models.h"
+
+namespace iorap::maintenance {
+
+// Clean up the database.
+// Remove all relevant data for old-version packages.
+void CleanUpDatabase(const db::DbHandle& db,
+ std::shared_ptr<binder::PackageVersionMap> version_map);
+} // namespace iorap::maintenance
+
+#endif // IORAP_SRC_MAINTENANCE_VERSION_UPDATE_H_
diff --git a/src/maintenance/main.cc b/src/maintenance/main.cc
new file mode 100644
index 0000000..16b2a35
--- /dev/null
+++ b/src/maintenance/main.cc
@@ -0,0 +1,194 @@
+// Copyright (C) 2019 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 "common/debug.h"
+#include "compiler/compiler.h"
+#include "maintenance/controller.h"
+#include "db/clean_up.h"
+
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/logging.h>
+
+#include <iostream>
+#include <optional>
+
+#if defined(IORAP_MAINTENANCE_MAIN)
+
+namespace iorap::maintenance {
+
+void Usage(char** argv) {
+ std::cerr << "Usage: " << argv[0] << " <path of sqlite db>" << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Compile the perfetto trace for an package and activity." << std::endl;
+ std::cerr << " The info of perfetto trace is stored in the sqlite db." << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Optional flags:" << std::endl;
+ std::cerr << " --package $,-p $ Package name." << std::endl;
+ std::cerr << " --version $,-ve $ Package version." << std::endl;
+ std::cerr << " --activity $,-a $ Activity name." << std::endl;
+ std::cerr << " --inode-textcache $,-it $ Resolve inode->filename from textcache." << std::endl;
+ std::cerr << " --help,-h Print this Usage." << std::endl;
+ std::cerr << " --recompile,-r Force re-compilation, which replace the existing compiled trace ." << std::endl;
+ std::cerr << " --purge-package,-pp Purge all files associated with a package." << std::endl;
+ std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
+ std::cerr << " --output-text,-ot Output ascii text instead of protobuf (default off)." << std::endl;
+ std::cerr << " --min_traces,-mt The min number of perfetto traces needed for compilation (default 1)."
+ << std::endl;
+ exit(1);
+}
+
+
+int Main(int argc, char** argv){
+ android::base::InitLogging(argv);
+ android::base::SetLogger(android::base::StderrLogger);
+
+ if (argc == 1) {
+ // Need at least 1 input file to do anything.
+ Usage(argv);
+ }
+
+ std::vector<std::string> arg_input_filenames;
+ std::optional<std::string> arg_package;
+ std::optional<std::string> arg_purge_package;
+ int arg_version = -1;
+ std::optional<std::string> arg_activity;
+ std::optional<std::string> arg_inode_textcache;
+ bool recompile = false;
+ bool enable_verbose = false;
+ bool arg_output_text = false;
+ uint64_t arg_min_traces = 1;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+ bool has_arg_next = (arg+1)<argc;
+ std::string arg_next = has_arg_next ? argv[arg+1] : "";
+
+ if (argstr == "--help" || argstr == "-h") {
+ Usage(argv);
+ } else if (argstr == "--package" || argstr == "-p") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --package <value>" << std::endl;
+ return 1;
+ }
+ arg_package = arg_next;
+ ++arg;
+ } else if (argstr == "--version" || argstr == "-ve") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --version <value>" << std::endl;
+ return 1;
+ }
+ int version;
+ if (!android::base::ParseInt<int>(arg_next, &version)) {
+ std::cerr << "Invalid --version " << arg_next << std::endl;
+ return 1;
+ }
+ arg_version = version;
+ ++arg;
+ } else if (argstr == "--activity" || argstr == "-a") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --activity <value>" << std::endl;
+ return 1;
+ }
+ arg_activity = arg_next;
+ ++arg;
+ } else if (argstr == "--inode-textcache" || argstr == "-it") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --inode-textcache <value>" << std::endl;
+ return 1;
+ }
+ arg_inode_textcache = arg_next;
+ ++arg;
+ } else if (argstr == "--purge-package" || argstr == "-pp") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --purge-package <value>" << std::endl;
+ return 1;
+ }
+ arg_purge_package = arg_next;
+ ++arg;
+ }
+ else if (argstr == "--verbose" || argstr == "-v") {
+ enable_verbose = true;
+ } else if (argstr == "--recompile" || argstr == "-r") {
+ recompile = true;
+ } else if (argstr == "--output-text" || argstr == "-ot") {
+ arg_output_text = true;
+ } else if (argstr == "--min_traces" || argstr == "-mt") {
+ if (!has_arg_next) {
+ std::cerr << "Missing --min_traces <value>" << std::endl;
+ return 1;
+ }
+ arg_min_traces = std::stoul(arg_next);
+ ++arg;
+ } else {
+ arg_input_filenames.push_back(argstr);
+ }
+ }
+
+ if (arg_input_filenames.empty()) {
+ LOG(ERROR) << "Missing filename to a sqlite database.";
+ Usage(argv);
+ } else if (arg_input_filenames.size() > 1) {
+ LOG(ERROR) << "More than one filename to a sqlite database.";
+ Usage(argv);
+ }
+
+ std::string db_path = arg_input_filenames[0];
+
+ if (enable_verbose) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+ LOG(VERBOSE) << "Verbose check";
+ LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::DEBUG);
+ }
+
+ if (arg_purge_package) {
+ db::CleanUpFilesForPackage(db_path, *arg_purge_package);
+ return 0;
+ // Don't do any more work because SchemaModel can only be created once.
+ }
+
+ maintenance::ControllerParameters params{
+ arg_output_text,
+ arg_inode_textcache,
+ enable_verbose,
+ recompile,
+ arg_min_traces,
+ std::make_shared<Exec>()};
+
+ int ret_code = 0;
+ if (arg_package && arg_activity) {
+ ret_code = !Compile(std::move(db_path),
+ std::move(*arg_package),
+ std::move(*arg_activity),
+ arg_version,
+ params);
+ } else if (arg_package) {
+ ret_code = !Compile(std::move(db_path), std::move(*arg_package), arg_version, params);
+ } else {
+ ret_code = !Compile(std::move(db_path), params);
+ }
+ return ret_code;
+}
+
+} // iorap::maintenance
+
+int main(int argc, char** argv) {
+ return ::iorap::maintenance::Main(argc, argv);
+}
+
+
+#endif // IORAP_MAINTENANCE_MAIN
diff --git a/src/manager/event_manager.cc b/src/manager/event_manager.cc
index 3b75b89..bae676b 100644
--- a/src/manager/event_manager.cc
+++ b/src/manager/event_manager.cc
@@ -14,94 +14,94 @@
* limitations under the License.
*/
+#include "binder/package_version_map.h"
#include "common/debug.h"
#include "common/expected.h"
+#include "common/printer.h"
+#include "common/rx_async.h"
+#include "common/trace.h"
+#include "db/app_component_name.h"
+#include "db/file_models.h"
+#include "db/models.h"
+#include "maintenance/controller.h"
+#include "maintenance/db_cleaner.h"
#include "manager/event_manager.h"
#include "perfetto/rx_producer.h"
+#include "prefetcher/read_ahead.h"
+#include "prefetcher/task_id.h"
+#include <android-base/chrono_utils.h>
+#include <android-base/strings.h>
#include <android-base/properties.h>
#include <rxcpp/rx.hpp>
+#include <server_configurable_flags/get_flags.h>
+#include <utils/misc.h>
+#include <utils/Trace.h>
#include <atomic>
+#include <filesystem>
#include <functional>
+#include <type_traits>
+#include <unordered_map>
using rxcpp::observe_on_one_worker;
namespace iorap::manager {
-using binder::RequestId;
using binder::AppLaunchEvent;
+using binder::DexOptEvent;
+using binder::JobScheduledEvent;
+using binder::RequestId;
+using binder::TaskResult;
+
+using common::AsyncPool;
+using common::RxAsync;
+
using perfetto::PerfettoStreamCommand;
using perfetto::PerfettoTraceProto;
-struct AppComponentName {
- std::string package;
- std::string activity_name;
+using db::AppComponentName;
- static bool HasAppComponentName(const std::string& s) {
- return s.find('/') != std::string::npos;
- }
+static std::atomic<bool> s_tracing_allowed{false};
+static std::atomic<bool> s_readahead_allowed{false};
+static std::atomic<uint64_t> s_min_traces{3};
- // "com.foo.bar/.A" -> {"com.foo.bar", ".A"}
- static AppComponentName FromString(const std::string& s) {
- constexpr const char delimiter = '/';
- std::string package = s.substr(0, delimiter);
+struct PackageBlacklister {
+ // "x.y.z;foo.bar.baz" colon-separated list of substrings
+ PackageBlacklister(std::string blacklist_string) {
+ LOG(VERBOSE) << "Configuring package blacklister with string: " << blacklist_string;
- std::string activity_name = s;
- activity_name.erase(0, s.find(delimiter) + sizeof(delimiter));
+ std::vector<std::string> split = ::android::base::Split(blacklist_string, ";");
- return {std::move(package), std::move(activity_name)};
- }
-
- // {"com.foo.bar", ".A"} -> "com.foo.bar/.A"
- std::string ToString() const {
- return package + "/" + activity_name;
+ // Ignore any l/r whitespace or empty strings.
+ for (const std::string& s : split) {
+ std::string t = ::android::base::Trim(s);
+ if (!t.empty()) {
+ LOG(INFO) << "Blacklisted package: " << t << "; will not optimize.";
+ packages_.push_back(t);
+ }
+ }
}
- /*
- * '/' is encoded into %2F
- * '%' is encoded into %25
- *
- * This allows the component name to be be used as a file name
- * ('/' is illegal due to being a path separator) with minimal
- * munging.
- */
+ PackageBlacklister() = default;
- // "com.foo.bar%2F.A%25" -> {"com.foo.bar", ".A%"}
- static AppComponentName FromUrlEncodedString(const std::string& s) {
- std::string cpy = s;
- Replace(cpy, "%2F", "/");
- Replace(cpy, "%25", "%");
+ bool IsBlacklisted(const std::string& package_name) const {
+ return std::find(packages_.begin(), packages_.end(), package_name) != packages_.end();
+ }
- return FromString(cpy);
+ bool IsBlacklisted(const AppComponentName& component_name) const {
+ return IsBlacklisted(component_name.package);
}
- // {"com.foo.bar", ".A%"} -> "com.foo.bar%2F.A%25"
- std::string ToUrlEncodedString() const {
- std::string s = ToString();
- Replace(s, "%", "%25");
- Replace(s, "/", "%2F");
- return s;
+ bool IsBlacklisted(const std::optional<AppComponentName>& component_name) const {
+ return component_name.has_value() && IsBlacklisted(component_name->package);
}
private:
- static bool Replace(std::string& str, const std::string& from, const std::string& to) {
- // TODO: call in a loop to replace all occurrences, not just the first one.
- const size_t start_pos = str.find(from);
- if (start_pos == std::string::npos) {
- return false;
- }
-
- str.replace(start_pos, from.length(), to);
-
- return true;
-}
+ std::vector<std::string> packages_;
};
-std::ostream& operator<<(std::ostream& os, const AppComponentName& name) {
- os << name.ToString();
- return os;
-}
+using PackageVersionMap = std::unordered_map<std::string, int64_t>;
// Main logic of the #OnAppLaunchEvent scan method.
//
@@ -112,26 +112,74 @@ std::ostream& operator<<(std::ostream& os, const AppComponentName& name) {
// of #scan to another.
struct AppLaunchEventState {
std::optional<AppComponentName> component_name_;
-
+ // Sequence ID is shared amongst the same app launch sequence,
+ // but changes whenever a new app launch sequence begins.
+ size_t sequence_id_ = static_cast<size_t>(-1);
+ std::optional<AppLaunchEvent::Temperature> temperature_;
+
+ // Push data to perfetto rx chain for associating
+ // the raw_trace with the history_id.
+ std::optional<rxcpp::subscriber<int>> history_id_subscriber_;
+ rxcpp::observable<int> history_id_observable_;
+
+ std::optional<uint64_t> intent_started_ns_;
+ std::optional<uint64_t> total_time_ns_;
+
+ // Used by kReportFullyDrawn to find the right history_id.
+ // We assume no interleaving between different sequences.
+ // This assumption is checked in the Java service code.
+ std::optional<uint64_t> recent_history_id_;
+
+ // labeled as 'shared' due to rx not being able to handle move-only objects.
+ // lifetime: in practice equivalent to unique_ptr.
+ std::shared_ptr<prefetcher::ReadAhead> read_ahead_;
+ bool allowed_readahead_{true};
+ bool is_read_ahead_{false};
+ std::optional<prefetcher::TaskId> read_ahead_task_;
+
+ bool allowed_tracing_{true};
bool is_tracing_{false};
std::optional<rxcpp::composite_subscription> rx_lifetime_;
std::vector<rxcpp::composite_subscription> rx_in_flight_;
+ PackageBlacklister package_blacklister_{};
+
borrowed<perfetto::RxProducerFactory*> perfetto_factory_; // not null
borrowed<observe_on_one_worker*> thread_; // not null
borrowed<observe_on_one_worker*> io_thread_; // not null
+ borrowed<AsyncPool*> async_pool_; // not null
+
+ std::shared_ptr<binder::PackageVersionMap> version_map_;
explicit AppLaunchEventState(borrowed<perfetto::RxProducerFactory*> perfetto_factory,
+ bool allowed_readahead,
+ bool allowed_tracing,
+ PackageBlacklister package_blacklister,
borrowed<observe_on_one_worker*> thread,
- borrowed<observe_on_one_worker*> io_thread) {
+ borrowed<observe_on_one_worker*> io_thread,
+ borrowed<AsyncPool*> async_pool,
+ std::shared_ptr<binder::PackageVersionMap> version_map)
+ : read_ahead_{std::make_shared<prefetcher::ReadAhead>()}
+ {
perfetto_factory_ = perfetto_factory;
DCHECK(perfetto_factory_ != nullptr);
+ allowed_readahead_ = allowed_readahead;
+ allowed_tracing_ = allowed_tracing;
+
+ package_blacklister_ = package_blacklister;
+
thread_ = thread;
DCHECK(thread_ != nullptr);
io_thread_ = io_thread;
DCHECK(io_thread_ != nullptr);
+
+ async_pool_ = async_pool;
+ DCHECK(async_pool_ != nullptr);
+
+ version_map_ = version_map;
+ DCHECK(version_map_ != nullptr);
}
// Updates the values in this struct only as a side effect.
@@ -141,11 +189,57 @@ struct AppLaunchEventState {
void OnNewEvent(const AppLaunchEvent& event) {
LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent: " << event;
+ android::ScopedTrace trace_db_init{ATRACE_TAG_ACTIVITY_MANAGER,
+ "IorapNativeService::OnAppLaunchEvent"};
+
using Type = AppLaunchEvent::Type;
+ DCHECK_GE(event.sequence_id, 0);
+ sequence_id_ = static_cast<size_t>(event.sequence_id);
+ allowed_readahead_ = s_readahead_allowed;
+ allowed_tracing_ = s_tracing_allowed;
+
switch (event.type) {
case Type::kIntentStarted: {
+ const std::string& package_name = event.intent_proto->component().package_name();
+ const std::string& class_name = event.intent_proto->component().class_name();
+ AppComponentName component_name{package_name, class_name};
+ component_name = component_name.Canonicalize();
+ component_name_ = component_name;
+
+ if (package_blacklister_.IsBlacklisted(component_name)) {
+ LOG(DEBUG) << "kIntentStarted: package " << component_name.package
+ << " ignored due to blacklisting.";
+ break;
+ }
+
+ // Create a new history ID chain for each new app start-up sequence.
+ auto history_id_observable = rxcpp::observable<>::create<int>(
+ [&](rxcpp::subscriber<int> subscriber) {
+ history_id_subscriber_ = std::move(subscriber);
+ LOG(VERBOSE) << " set up the history id subscriber ";
+ })
+ .tap([](int history_id) { LOG(VERBOSE) << " tap rx history id = " << history_id; })
+ .replay(1); // Remember the history id in case we subscribe too late.
+
+ history_id_observable_ = history_id_observable;
+
+ // Immediately turn observable hot, creating the subscriber.
+ history_id_observable.connect();
+
DCHECK(!IsTracing());
+
+ // The time should be set before perfetto tracing.
+ // Record the timestamp even no perfetto tracing is triggered,
+ // because the tracing may start in the following ActivityLaunched
+ // event. Otherwise, there will be no starting timestamp and
+ // trace without starting timestamp is not considered for compilation.
+ if (event.timestamp_nanos >= 0) {
+ intent_started_ns_ = event.timestamp_nanos;
+ } else {
+ LOG(WARNING) << "Negative event timestamp: " << event.timestamp_nanos;
+ }
+
// Optimistically start tracing if we have the activity in the intent.
if (!event.intent_proto->has_component()) {
// Can't do anything if there is no component in the proto.
@@ -153,51 +247,93 @@ struct AppLaunchEventState {
break;
}
- const std::string& package_name = event.intent_proto->component().package_name();
- const std::string& class_name = event.intent_proto->component().class_name();
- AppComponentName component_name{package_name, class_name};
-
- component_name_ = component_name;
- rx_lifetime_ = StartTracing(std::move(component_name));
+ if (allowed_readahead_) {
+ StartReadAhead(sequence_id_, component_name);
+ }
+ if (allowed_tracing_ && !IsReadAhead()) {
+ rx_lifetime_ = StartTracing(std::move(component_name));
+ }
break;
}
case Type::kIntentFailed:
+ if (package_blacklister_.IsBlacklisted(component_name_)) {
+ LOG(VERBOSE) << "kIntentFailed: package " << component_name_->package
+ << " ignored due to blacklisting.";
+ break;
+ }
+
AbortTrace();
+ AbortReadAhead();
+
+ if (history_id_subscriber_) {
+ history_id_subscriber_->on_error(rxcpp::util::make_error_ptr(
+ std::ios_base::failure("Aborting due to intent failed")));
+ history_id_subscriber_ = std::nullopt;
+ }
+
break;
case Type::kActivityLaunched: {
+ const std::string& title = event.activity_record_proto->identifier().title();
+ if (!AppComponentName::HasAppComponentName(title)) {
+ // Proto comment claim this is sometimes a window title.
+ // We need the actual 'package/component' here, so just ignore it if it's a title.
+ LOG(WARNING) << "App launched without a component name: " << event;
+ break;
+ }
+
+ AppComponentName component_name = AppComponentName::FromString(title);
+ component_name = component_name.Canonicalize();
+ component_name_ = component_name;
+
+ if (package_blacklister_.IsBlacklisted(component_name_)) {
+ LOG(VERBOSE) << "kActivityLaunched: package " << component_name_->package
+ << " ignored due to blacklisting.";
+ break;
+ }
+
// Cancel tracing for warm/hot.
// Restart tracing if the activity was unexpected.
AppLaunchEvent::Temperature temperature = event.temperature;
+ temperature_ = temperature;
if (temperature != AppLaunchEvent::Temperature::kCold) {
LOG(DEBUG) << "AppLaunchEventState#OnNewEvent aborting trace due to non-cold temperature";
+
AbortTrace();
- } else if (!IsTracing()) { // and the temperature is Cold.
+ AbortReadAhead();
+ } else if (!IsTracing() && !IsReadAhead()) { // and the temperature is Cold.
// Start late trace when intent didn't have a component name
LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent need to start new trace";
- const std::string& title = event.activity_record_proto->identifier().title();
- if (!AppComponentName::HasAppComponentName(title)) {
- // Proto comment claim this is sometimes a window title.
- // We need the actual 'package/component' here, so just ignore it if it's a title.
- LOG(WARNING) << "App launched without a component name: " << event;
- break;
+ if (allowed_readahead_ && !IsReadAhead()) {
+ StartReadAhead(sequence_id_, component_name);
+ }
+ if (allowed_tracing_ && !IsTracing() && !IsReadAhead()) {
+ rx_lifetime_ = StartTracing(std::move(component_name));
}
-
- AppComponentName component_name = AppComponentName::FromString(title);
-
- component_name_ = component_name;
- rx_lifetime_ = StartTracing(std::move(component_name));
} else {
// FIXME: match actual component name against intent component name.
// abort traces if they don't match.
- LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent already tracing";
+ if (allowed_tracing_) {
+ LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent already tracing";
+ }
+ LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent already doing readahead";
}
break;
}
case Type::kActivityLaunchFinished:
+ if (package_blacklister_.IsBlacklisted(component_name_)) {
+ LOG(VERBOSE) << "kActivityLaunchFinished: package " << component_name_->package
+ << " ignored due to blacklisting.";
+ break;
+ }
+
+ if (event.timestamp_nanos >= 0) {
+ total_time_ns_ = event.timestamp_nanos;
+ }
+ RecordDbLaunchHistory();
// Finish tracing and collect trace buffer.
//
// TODO: this happens automatically when perfetto finishes its
@@ -205,22 +341,138 @@ struct AppLaunchEventState {
if (IsTracing()) {
MarkPendingTrace();
}
+ FinishReadAhead();
break;
case Type::kActivityLaunchCancelled:
+ if (package_blacklister_.IsBlacklisted(component_name_)) {
+ LOG(VERBOSE) << "kActivityLaunchCancelled: package " << component_name_->package
+ << " ignored due to blacklisting.";
+ break;
+ }
+
// Abort tracing.
AbortTrace();
+ AbortReadAhead();
+ break;
+ case Type::kReportFullyDrawn: {
+ if (package_blacklister_.IsBlacklisted(component_name_)) {
+ LOG(VERBOSE) << "kReportFullyDrawn: package " << component_name_->package
+ << " ignored due to blacklisting.";
+ break;
+ }
+
+ if (!recent_history_id_) {
+ LOG(WARNING) << "Dangling kReportFullyDrawn event";
+ return;
+ }
+ UpdateReportFullyDrawn(*recent_history_id_, event.timestamp_nanos);
+ recent_history_id_ = std::nullopt;
break;
+ }
default:
DCHECK(false) << "invalid type: " << event; // binder layer should've rejected this.
LOG(ERROR) << "invalid type: " << event; // binder layer should've rejected this.
}
}
+ // Is there an in-flight readahead task currently?
+ bool IsReadAhead() const {
+ return read_ahead_task_.has_value();
+ }
+
+ // Gets the compiled trace.
+ // If a compiled trace exists in sqlite, use that one. Otherwise, try
+ // to find a prebuilt one.
+ std::optional<std::string> GetCompiledTrace(const AppComponentName& component_name) {
+ ScopedFormatTrace atrace_get_compiled_trace(ATRACE_TAG_ACTIVITY_MANAGER, "GetCompiledTrace");
+ // Firstly, try to find the compiled trace from sqlite.
+ android::base::Timer timer{};
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+ std::optional<int> version =
+ version_map_->GetOrQueryPackageVersion(component_name.package);
+ if (!version) {
+ LOG(DEBUG) << "The version is NULL, maybe package manager is down.";
+ return std::nullopt;
+ }
+ db::VersionedComponentName vcn{component_name.package,
+ component_name.activity_name,
+ *version};
+
+ std::optional<db::PrefetchFileModel> compiled_trace =
+ db::PrefetchFileModel::SelectByVersionedComponentName(db, vcn);
+
+ std::chrono::milliseconds duration_ms = timer.duration();
+ LOG(DEBUG) << "EventManager: Looking up compiled trace done in "
+ << duration_ms.count() // the count of ticks.
+ << "ms.";
+
+ if (compiled_trace) {
+ if (std::filesystem::exists(compiled_trace->file_path)) {
+ return compiled_trace->file_path;
+ } else {
+ LOG(DEBUG) << "Compiled trace in sqlite doesn't exists. file_path: "
+ << compiled_trace->file_path;
+ }
+ }
+
+ LOG(DEBUG) << "Cannot find compiled trace in sqlite for package_name: "
+ << component_name.package
+ << " activity_name: "
+ << component_name.activity_name;
+
+ // If sqlite doesn't have the compiled trace, try the prebuilt path.
+ std::string file_path = "/product/iorap-trace/";
+ file_path += component_name.ToMakeFileSafeEncodedPkgString();
+ file_path += ".compiled_trace.pb";
+
+ if (std::filesystem::exists(file_path)) {
+ return file_path;
+ }
+
+ LOG(DEBUG) << "Prebuilt compiled trace doesn't exists. file_path: "
+ << file_path;
+
+ return std::nullopt;
+ }
+
+ void StartReadAhead(size_t id, const AppComponentName& component_name) {
+ DCHECK(allowed_readahead_);
+ DCHECK(!IsReadAhead());
+
+ std::optional<std::string> file_path = GetCompiledTrace(component_name);
+ if (!file_path) {
+ LOG(VERBOSE) << "Cannot find a compiled trace.";
+ return;
+ }
+
+ prefetcher::TaskId task{id, *file_path};
+ read_ahead_->BeginTask(task);
+ // TODO: non-void return signature?
+
+ read_ahead_task_ = std::move(task);
+ }
+
+ void FinishReadAhead() {
+ // if no readahead task exist, do nothing.
+ if (!IsReadAhead()){
+ return;
+ }
+
+ read_ahead_->FinishTask(*read_ahead_task_);
+ read_ahead_task_ = std::nullopt;
+ }
+
+ void AbortReadAhead() {
+ FinishReadAhead();
+ }
+
bool IsTracing() const {
return is_tracing_;
}
- rxcpp::composite_subscription StartTracing(AppComponentName component_name) {
+ std::optional<rxcpp::composite_subscription> StartTracing(
+ AppComponentName component_name) {
+ DCHECK(allowed_tracing_);
DCHECK(!IsTracing());
auto /*observable<PerfettoStreamCommand>*/ perfetto_commands =
@@ -245,30 +497,73 @@ struct AppLaunchEventState {
rxcpp::composite_subscription lifetime;
- trace_proto_stream
+ auto stream_via_threads = trace_proto_stream
.tap([](const PerfettoTraceProto& trace_proto) {
LOG(VERBOSE) << "StartTracing -- PerfettoTraceProto received (1)";
})
+ .combine_latest(history_id_observable_)
.observe_on(*thread_) // All work prior to 'observe_on' is handled on thread_.
.subscribe_on(*thread_) // All work prior to 'observe_on' is handled on thread_.
.observe_on(*io_thread_) // Write data on an idle-class-priority thread.
- .tap([](const PerfettoTraceProto& trace_proto) {
+ .tap([](std::tuple<PerfettoTraceProto, int> trace_tuple) {
LOG(VERBOSE) << "StartTracing -- PerfettoTraceProto received (2)";
- })
- .as_blocking() // TODO: remove.
- .subscribe(/*out*/lifetime,
- /*on_next*/[component_name]
- (PerfettoTraceProto trace_proto) {
- std::string file_path = "/data/misc/iorapd/";
- file_path += component_name.ToUrlEncodedString();
- file_path += ".perfetto_trace.pb";
+ });
- // TODO: timestamp each file into a subdirectory.
+ std::optional<int> version =
+ version_map_->GetOrQueryPackageVersion(component_name_->package);
+ if (!version) {
+ LOG(DEBUG) << "The version is NULL, maybe package manager is down.";
+ return std::nullopt;
+ }
+ db::VersionedComponentName versioned_component_name{component_name.package,
+ component_name.activity_name,
+ *version};
+ lifetime = RxAsync::SubscribeAsync(*async_pool_,
+ std::move(stream_via_threads),
+ /*on_next*/[versioned_component_name]
+ (std::tuple<PerfettoTraceProto, int> trace_tuple) {
+ PerfettoTraceProto& trace_proto = std::get<0>(trace_tuple);
+ int history_id = std::get<1>(trace_tuple);
+
+ db::PerfettoTraceFileModel file_model =
+ db::PerfettoTraceFileModel::CalculateNewestFilePath(versioned_component_name);
+
+ std::string file_path = file_model.FilePath();
+
+ ScopedFormatTrace atrace_write_to_file(ATRACE_TAG_ACTIVITY_MANAGER,
+ "Perfetto Write Trace To File %s",
+ file_path.c_str());
+
+ if (!file_model.MkdirWithParents()) {
+ LOG(ERROR) << "Cannot save TraceBuffer; failed to mkdirs " << file_path;
+ return;
+ }
if (!trace_proto.WriteFullyToFile(file_path)) {
LOG(ERROR) << "Failed to save TraceBuffer to " << file_path;
} else {
LOG(INFO) << "Perfetto TraceBuffer saved to file: " << file_path;
+
+ ScopedFormatTrace atrace_update_raw_traces_table(
+ ATRACE_TAG_ACTIVITY_MANAGER,
+ "update raw_traces table history_id = %d",
+ history_id);
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+ std::optional<db::RawTraceModel> raw_trace =
+ db::RawTraceModel::Insert(db, history_id, file_path);
+
+ if (!raw_trace) {
+ LOG(ERROR) << "Failed to insert raw_traces for " << file_path;
+ } else {
+ LOG(VERBOSE) << "Inserted into db: " << *raw_trace;
+
+ ScopedFormatTrace atrace_delete_older_files(
+ ATRACE_TAG_ACTIVITY_MANAGER,
+ "Delete older trace files for package");
+
+ // Ensure we don't have too many files per-app.
+ db::PerfettoTraceFileModel::DeleteOlderFiles(db, versioned_component_name);
+ }
}
},
/*on_error*/[](rxcpp::util::error_ptr err) {
@@ -282,12 +577,23 @@ struct AppLaunchEventState {
void AbortTrace() {
LOG(VERBOSE) << "AppLaunchEventState - AbortTrace";
+
+ // if the tracing is not running, do nothing.
+ if (!IsTracing()){
+ return;
+ }
+
is_tracing_ = false;
if (rx_lifetime_) {
// TODO: it would be good to call perfetto Destroy.
+ rx_in_flight_.erase(std::remove(rx_in_flight_.begin(),
+ rx_in_flight_.end(), *rx_lifetime_),
+ rx_in_flight_.end());
+
LOG(VERBOSE) << "AppLaunchEventState - AbortTrace - Unsubscribe";
rx_lifetime_->unsubscribe();
+
rx_lifetime_.reset();
}
}
@@ -309,8 +615,187 @@ struct AppLaunchEventState {
LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace - lifetime was empty";
}
+ is_tracing_ = false;
// FIXME: how do we clear this vector?
}
+
+ void RecordDbLaunchHistory() {
+ std::optional<db::AppLaunchHistoryModel> history = InsertDbLaunchHistory();
+
+ // RecordDbLaunchHistory happens-after kIntentStarted
+ if (!history_id_subscriber_.has_value()) {
+ LOG(WARNING) << "Logic error? Should always have a subscriber here.";
+ return;
+ }
+
+ // Ensure that the history id rx chain is terminated either with an error or with
+ // the newly inserted app_launch_histories.id
+ if (!history) {
+ history_id_subscriber_->on_error(rxcpp::util::make_error_ptr(
+ std::ios_base::failure("Failed to insert history id")));
+ recent_history_id_ = std::nullopt;
+ } else {
+ // Note: we must have already subscribed, or this value will disappear.
+ LOG(VERBOSE) << "history_id_subscriber on_next history_id=" << history->id;
+ history_id_subscriber_->on_next(history->id);
+ history_id_subscriber_->on_completed();
+
+ recent_history_id_ = history->id;
+ }
+ history_id_subscriber_ = std::nullopt;
+ }
+
+ std::optional<db::AppLaunchHistoryModel> InsertDbLaunchHistory() {
+ // TODO: deferred queue into a different lower priority thread.
+ if (!component_name_ || !temperature_) {
+ LOG(VERBOSE) << "Skip RecordDbLaunchHistory, no component name available.";
+
+ return std::nullopt;
+ }
+
+ android::ScopedTrace trace{ATRACE_TAG_ACTIVITY_MANAGER,
+ "IorapNativeService::RecordDbLaunchHistory"};
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+
+ using namespace iorap::db;
+
+ std::optional<int> version =
+ version_map_->GetOrQueryPackageVersion(component_name_->package);
+ if (!version) {
+ LOG(DEBUG) << "The version is NULL, maybe package manager is down.";
+ return std::nullopt;
+ }
+ std::optional<ActivityModel> activity =
+ ActivityModel::SelectOrInsert(db,
+ component_name_->package,
+ *version,
+ component_name_->activity_name);
+
+ if (!activity) {
+ LOG(WARNING) << "Failed to query activity row for : " << *component_name_;
+ return std::nullopt;
+ }
+
+ auto temp = static_cast<db::AppLaunchHistoryModel::Temperature>(*temperature_);
+
+ std::optional<AppLaunchHistoryModel> alh =
+ AppLaunchHistoryModel::Insert(db,
+ activity->id,
+ temp,
+ IsTracing(),
+ IsReadAhead(),
+ intent_started_ns_,
+ total_time_ns_,
+ // ReportFullyDrawn event normally occurs after this. Need update later.
+ /* report_fully_drawn_ns= */ std::nullopt);
+ //Repo
+ if (!alh) {
+ LOG(WARNING) << "Failed to insert app_launch_histories row";
+ return std::nullopt;
+ }
+
+ LOG(VERBOSE) << "RecordDbLaunchHistory: " << *alh;
+ return alh;
+ }
+
+ void UpdateReportFullyDrawn(int history_id, uint64_t timestamp_ns) {
+ LOG(DEBUG) << "Update kReportFullyDrawn for history_id:"
+ << history_id
+ << " timestamp_ns: "
+ << timestamp_ns;
+
+ android::ScopedTrace trace{ATRACE_TAG_ACTIVITY_MANAGER,
+ "IorapNativeService::UpdateReportFullyDrawn"};
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+
+ bool result =
+ db::AppLaunchHistoryModel::UpdateReportFullyDrawn(db,
+ history_id,
+ timestamp_ns);
+
+ if (!result) {
+ LOG(WARNING) << "Failed to update app_launch_histories row";
+ }
+ }
+};
+
+struct AppLaunchEventDefender {
+ binder::AppLaunchEvent::Type last_event_type_{binder::AppLaunchEvent::Type::kUninitialized};
+
+ enum class Result {
+ kAccept, // Pass-through the new event.
+ kOverwrite, // Overwrite the new event with a different event.
+ kReject // Completely reject the new event, it will not be delivered.
+ };
+
+ Result OnAppLaunchEvent(binder::RequestId request_id,
+ const binder::AppLaunchEvent& event,
+ binder::AppLaunchEvent* overwrite) {
+ using Type = binder::AppLaunchEvent::Type;
+ CHECK(overwrite != nullptr);
+
+ // Ensure only legal transitions are allowed.
+ switch (last_event_type_) {
+ case Type::kUninitialized:
+ case Type::kIntentFailed:
+ case Type::kActivityLaunchCancelled:
+ case Type::kReportFullyDrawn: { // From a terminal state, only go to kIntentStarted
+ if (event.type != Type::kIntentStarted) {
+ LOG(WARNING) << "Rejecting transition from " << last_event_type_ << " to " << event.type;
+ last_event_type_ = Type::kUninitialized;
+ return Result::kReject;
+ } else {
+ LOG(VERBOSE) << "Accept transition from " << last_event_type_ << " to " << event.type;
+ last_event_type_ = event.type;
+ return Result::kAccept;
+ }
+ }
+ case Type::kIntentStarted: {
+ if (event.type == Type::kIntentFailed ||
+ event.type == Type::kActivityLaunched) {
+ LOG(VERBOSE) << "Accept transition from " << last_event_type_ << " to " << event.type;
+ last_event_type_ = event.type;
+ return Result::kAccept;
+ } else {
+ LOG(WARNING) << "Overwriting transition from kIntentStarted to "
+ << event.type << " into kIntentFailed";
+ last_event_type_ = Type::kIntentFailed;
+
+ *overwrite = event;
+ overwrite->type = Type::kIntentFailed;
+ return Result::kOverwrite;
+ }
+ }
+ case Type::kActivityLaunched: {
+ if (event.type == Type::kActivityLaunchFinished ||
+ event.type == Type::kActivityLaunchCancelled) {
+ LOG(VERBOSE) << "Accept transition from " << last_event_type_ << " to " << event.type;
+ last_event_type_ = event.type;
+ return Result::kAccept;
+ } else {
+ LOG(WARNING) << "Overwriting transition from kActivityLaunched to "
+ << event.type << " into kActivityLaunchCancelled";
+ last_event_type_ = Type::kActivityLaunchCancelled;
+
+ *overwrite = event;
+ overwrite->type = Type::kActivityLaunchCancelled;
+ return Result::kOverwrite;
+ }
+ }
+ case Type::kActivityLaunchFinished: {
+ if (event.type == Type::kIntentStarted ||
+ event.type == Type::kReportFullyDrawn) {
+ LOG(VERBOSE) << "Accept transition from " << last_event_type_ << " to " << event.type;
+ last_event_type_ = event.type;
+ return Result::kAccept;
+ } else {
+ LOG(WARNING) << "Rejecting transition from " << last_event_type_ << " to " << event.type;
+ last_event_type_ = Type::kUninitialized;
+ return Result::kReject;
+ }
+ }
+ }
+ }
};
// Convert callback pattern into reactive pattern.
@@ -387,6 +872,70 @@ struct AppLaunchEventSubject {
std::optional<rxcpp::subscriber<RefWrapper>> subscriber_;
};
+// Convert callback pattern into reactive pattern.
+struct JobScheduledEventSubject {
+ JobScheduledEventSubject() {}
+
+ void Subscribe(rxcpp::subscriber<std::pair<RequestId, JobScheduledEvent>> subscriber) {
+ DCHECK(ready_ != true) << "Cannot Subscribe twice";
+
+ subscriber_ = std::move(subscriber);
+
+ // Release edge of synchronizes-with AcquireIsReady.
+ ready_.store(true);
+ }
+
+ void OnNext(RequestId request_id, JobScheduledEvent e) {
+ if (!AcquireIsReady()) {
+ return;
+ }
+
+ if (!subscriber_->is_subscribed()) {
+ return;
+ }
+
+ subscriber_->on_next(std::pair<RequestId, JobScheduledEvent>{std::move(request_id), std::move(e)});
+
+ }
+
+ void OnCompleted() {
+ if (!AcquireIsReady()) {
+ return;
+ }
+
+ subscriber_->on_completed();
+ }
+
+ private:
+ bool AcquireIsReady() {
+ // Synchronizes-with the release-edge in Subscribe.
+ // This can happen much later, only once the subscription actually happens.
+
+ // However, as far as I know, 'rxcpp::subscriber' is not thread safe,
+ // (but the observable chain itself can be made thread-safe via #observe_on, etc).
+ // so we must avoid reading it until it has been fully synchronized.
+ //
+ // TODO: investigate rxcpp subscribers and see if we can get rid of this atomics,
+ // to make it simpler.
+ return ready_.load();
+ }
+
+ // TODO: also track the RequestId ?
+
+ std::atomic<bool> ready_{false};
+
+ std::optional<rxcpp::subscriber<std::pair<RequestId, JobScheduledEvent>>> subscriber_;
+};
+
+std::ostream& operator<<(std::ostream& os, const android::content::pm::PackageChangeEvent& event) {
+ os << "PackageChangeEvent{";
+ os << "packageName=" << event.packageName << ",";
+ os << "version=" << event.version << ",";
+ os << "lastUpdateTimeMillis=" << event.lastUpdateTimeMillis;
+ os << "}";
+ return os;
+}
+
class EventManager::Impl {
public:
Impl(/*borrow*/perfetto::RxProducerFactory& perfetto_factory)
@@ -394,15 +943,36 @@ class EventManager::Impl {
worker_thread_(rxcpp::observe_on_new_thread()),
worker_thread2_(rxcpp::observe_on_new_thread()),
io_thread_(perfetto::ObserveOnNewIoThread()) {
+ // Try to create version map
+ RetryCreateVersionMap();
- // TODO: read all properties from one config class.
- tracing_allowed_ = ::android::base::GetBoolProperty("iorapd.perfetto.enable", /*default*/false);
+ iorap::common::StderrLogPrinter printer{"iorapd"};
+ RefreshSystemProperties(printer);
- if (tracing_allowed_) {
- rx_lifetime_ = InitializeRxGraph();
- } else {
- LOG(WARNING) << "Tracing disabled by iorapd.perfetto.enable=false";
- }
+ rx_lifetime_ = InitializeRxGraph();
+ rx_lifetime_jobs_ = InitializeRxGraphForJobScheduledEvents();
+
+ android::add_sysprop_change_callback(&Impl::OnSyspropChanged, /*priority*/-10000);
+ }
+
+ void RetryCreateVersionMap() {
+ android::base::Timer timer{};
+ version_map_ = binder::PackageVersionMap::Create();
+ std::chrono::milliseconds duration_ms = timer.duration();
+ LOG(DEBUG) << "Got versions for "
+ << version_map_->Size()
+ << " packages in "
+ << duration_ms.count()
+ << "ms";
+ }
+
+ void SetTaskResultCallbacks(std::shared_ptr<TaskResultCallbacks> callbacks) {
+ DCHECK(callbacks_.expired());
+ callbacks_ = callbacks;
+ }
+
+ void Join() {
+ async_pool_.Join();
}
bool OnAppLaunchEvent(RequestId request_id,
@@ -411,11 +981,75 @@ class EventManager::Impl {
<< "request_id=" << request_id.request_id << ","
<< event;
- app_launch_event_subject_.OnNext(event);
+ // Filter any incoming events through a defender that enforces
+ // that all state transitions are as contractually documented in
+ // ActivityMetricsLaunchObserver's javadoc.
+ AppLaunchEvent overwrite_event{};
+ AppLaunchEventDefender::Result result =
+ app_launch_event_defender_.OnAppLaunchEvent(request_id, event, /*out*/&overwrite_event);
+
+ switch (result) {
+ case AppLaunchEventDefender::Result::kAccept:
+ app_launch_event_subject_.OnNext(event);
+ return true;
+ case AppLaunchEventDefender::Result::kOverwrite:
+ app_launch_event_subject_.OnNext(overwrite_event);
+ return false;
+ case AppLaunchEventDefender::Result::kReject:
+ // Intentionally left-empty: we drop the event completely.
+ return false;
+ }
+
+ // In theory returns BAD_VALUE to the other side of this binder connection.
+ // In practice we use 'oneway' flags so this doesn't matter on a regular build.
+ return false;
+ }
+
+ bool OnDexOptEvent(RequestId request_id,
+ const DexOptEvent& event) {
+ LOG(VERBOSE) << "EventManager::OnDexOptEvent("
+ << "request_id=" << request_id.request_id << ","
+ << event.package_name
+ << ")";
+
+ return PurgePackage(event.package_name);
+ }
+
+ bool OnJobScheduledEvent(RequestId request_id,
+ const JobScheduledEvent& event) {
+ LOG(VERBOSE) << "EventManager::OnJobScheduledEvent("
+ << "request_id=" << request_id.request_id << ",event=TODO).";
+
+ job_scheduled_event_subject_.OnNext(std::move(request_id), event);
+ return true; // No errors.
+ }
+
+ bool OnPackageChanged(const android::content::pm::PackageChangeEvent& event) {
+ LOG(DEBUG) << "Received " << event;
+ if (event.isDeleted) {
+ // Do nothing if the package is deleted rignt now.
+ // The package will be removed from db during maintenance.
+ return true;
+ }
+ // Update the version map.
+ if (version_map_->Update(event.packageName, event.version)) {
+ return true;
+ }
+
+ // Sometimes a package is updated without any version change.
+ // Clean it up in this case.
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+ db::CleanUpFilesForPackage(db, event.packageName, event.version);
return true;
}
+ void Dump(/*borrow*/::android::Printer& printer) {
+ ::iorap::prefetcher::ReadAhead::Dump(printer);
+ ::iorap::perfetto::PerfettoConsumerImpl::Dump(/*borrow*/printer);
+ ::iorap::maintenance::Dump(db::SchemaModel::GetSingleton(), printer);
+ }
+
rxcpp::composite_subscription InitializeRxGraph() {
LOG(VERBOSE) << "EventManager::InitializeRxGraph";
@@ -426,7 +1060,21 @@ class EventManager::Impl {
rxcpp::composite_subscription lifetime;
- AppLaunchEventState initial_state{&perfetto_factory_, &worker_thread2_, &io_thread_};
+ if (!tracing_allowed_) {
+ LOG(WARNING) << "Tracing disabled by system property";
+ }
+ if (!readahead_allowed_) {
+ LOG(WARNING) << "Readahead disabled by system property";
+ }
+
+ AppLaunchEventState initial_state{&perfetto_factory_,
+ readahead_allowed_,
+ tracing_allowed_,
+ package_blacklister_,
+ &worker_thread2_,
+ &io_thread_,
+ &async_pool_,
+ version_map_};
app_launch_events_
.subscribe_on(worker_thread_)
.scan(std::move(initial_state),
@@ -442,12 +1090,227 @@ class EventManager::Impl {
return lifetime;
}
+ // Runs the maintenance code to compile perfetto traces to compiled
+ // trace.
+ void StartMaintenance(bool output_text,
+ std::optional<std::string> inode_textcache,
+ bool verbose,
+ bool recompile,
+ uint64_t min_traces) {
+ ScopedFormatTrace atrace_bg_scope(ATRACE_TAG_PACKAGE_MANAGER,
+ "Background Job Scope");
+
+ {
+ ScopedFormatTrace atrace_update_versions(ATRACE_TAG_PACKAGE_MANAGER,
+ "Update package versions map cache");
+ // Update the version map.
+ version_map_->UpdateAll();
+ }
+
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+ {
+ ScopedFormatTrace atrace_cleanup_db(ATRACE_TAG_PACKAGE_MANAGER,
+ "Clean up obsolete data in database");
+ // Cleanup the obsolete data in the database.
+ maintenance::CleanUpDatabase(db, version_map_);
+ }
+
+ {
+ ScopedFormatTrace atrace_compile_apps(ATRACE_TAG_PACKAGE_MANAGER,
+ "Compile apps on device");
+ // Compilation
+ maintenance::ControllerParameters params{
+ output_text,
+ inode_textcache,
+ verbose,
+ recompile,
+ min_traces,
+ std::make_shared<maintenance::Exec>()};
+
+ LOG(DEBUG) << "StartMaintenance: min_traces=" << min_traces;
+ maintenance::CompileAppsOnDevice(db, params);
+ }
+ }
+
+ rxcpp::composite_subscription InitializeRxGraphForJobScheduledEvents() {
+ LOG(VERBOSE) << "EventManager::InitializeRxGraphForJobScheduledEvents";
+
+ using RequestAndJobEvent = std::pair<RequestId, JobScheduledEvent>;
+
+ job_scheduled_events_ = rxcpp::observable<>::create<RequestAndJobEvent>(
+ [&](rxcpp::subscriber<RequestAndJobEvent> subscriber) {
+ job_scheduled_event_subject_.Subscribe(std::move(subscriber));
+ });
+
+ rxcpp::composite_subscription lifetime;
+
+ job_scheduled_events_
+ .observe_on(worker_thread_) // async handling.
+ .tap([this](const RequestAndJobEvent& e) {
+ LOG(VERBOSE) << "EventManager#JobScheduledEvent#tap(1) - job begins";
+ this->NotifyProgress(e.first, TaskResult{TaskResult::State::kBegan});
+
+ StartMaintenance(/*output_text=*/false,
+ /*inode_textcache=*/std::nullopt,
+ /*verbose=*/false,
+ /*recompile=*/false,
+ s_min_traces);
+
+ // TODO: probably this shouldn't be emitted until most of the usual DCHECKs
+ // (for example, validate a job isn't already started, the request is not reused, etc).
+ // In this way we could block from the client until it sees 'kBegan' and Log.wtf otherwise.
+ })
+ .tap([](const RequestAndJobEvent& e) {
+ // TODO. Actual work.
+ LOG(VERBOSE) << "EventManager#JobScheduledEvent#tap(2) - job is being processed";
+
+ // TODO: abort functionality for in-flight jobs.
+ //
+ // maybe something like scan that returns an observable<Job> + flat map to that job.
+ // then we could unsubscribe from the scan to do a partial abort? need to try it and see if it works.
+ //
+ // other option is to create a new outer subscription for each job id which seems less ideal.
+ })
+ .subscribe(/*out*/lifetime,
+ /*on_next*/
+ [this](const RequestAndJobEvent& e) {
+ LOG(VERBOSE) << "EventManager#JobScheduledEvent#subscribe - job completed";
+ this->NotifyComplete(e.first, TaskResult{TaskResult::State::kCompleted});
+ }
+#if 0
+ ,
+ /*on_error*/
+ [](rxcpp::util::error_ptr err) {
+ LOG(ERROR) << "Scheduled job event failed: " << rxcpp::util::what(err);
+
+ //std::shared_ptr<TaskResultCallbacks> callbacks = callbacks_.lock();
+ //if (callbacks != nullptr) {
+ // FIXME: How do we get the request ID back out of the error? Seems like a problem.
+ // callbacks->OnComplete(, TaskResult{TaskResult::kError});
+ // We may have to wrap with an iorap::expected instead of using on_error.
+ //}
+
+ // FIXME: need to add a 'OnErrorResumeNext' operator?
+ DCHECK(false) << "forgot to implement OnErrorResumeNext";
+ }
+#endif
+ );
+
+ // TODO: error output should happen via an observable.
+
+ return lifetime;
+ }
+
+ void NotifyComplete(RequestId request_id, TaskResult result) {
+ std::shared_ptr<TaskResultCallbacks> callbacks = callbacks_.lock();
+ if (callbacks != nullptr) {
+ callbacks->OnComplete(std::move(request_id), std::move(result));
+ } else {
+ LOG(WARNING) << "EventManager: TaskResultCallbacks may have been released early";
+ }
+ }
+
+ void NotifyProgress(RequestId request_id, TaskResult result) {
+ std::shared_ptr<TaskResultCallbacks> callbacks = callbacks_.lock();
+ if (callbacks != nullptr) {
+ callbacks->OnProgress(std::move(request_id), std::move(result));
+ } else {
+ LOG(WARNING) << "EventManager: TaskResultCallbacks may have been released early";
+ }
+ }
+
+ static void OnSyspropChanged() {
+ LOG(DEBUG) << "OnSyspropChanged";
+ }
+
+ void RefreshSystemProperties(::android::Printer& printer) {
+ // TODO: read all properties from one config class.
+ // PH properties do not work if they contain ".". "_" was instead used here.
+ const char* ph_namespace = "runtime_native_boot";
+ tracing_allowed_ = server_configurable_flags::GetServerConfigurableFlag(
+ ph_namespace,
+ "iorap_perfetto_enable",
+ ::android::base::GetProperty("iorapd.perfetto.enable", /*default*/"true")) == "true";
+ s_tracing_allowed = tracing_allowed_;
+ printer.printFormatLine("iorapd.perfetto.enable = %s", tracing_allowed_ ? "true" : "false");
+
+ readahead_allowed_ = server_configurable_flags::GetServerConfigurableFlag(
+ ph_namespace,
+ "iorap_readahead_enable",
+ ::android::base::GetProperty("iorapd.readahead.enable", /*default*/"true")) == "true";
+ s_readahead_allowed = readahead_allowed_;
+ printer.printFormatLine("iorapd.readahead.enable = %s", s_readahead_allowed ? "true" : "false");
+
+ s_min_traces =
+ ::android::base::GetUintProperty<uint64_t>("iorapd.maintenance.min_traces", /*default*/1);
+ uint64_t min_traces = s_min_traces;
+ printer.printFormatLine("iorapd.maintenance.min_traces = %" PRIu64, min_traces);
+
+ package_blacklister_ = PackageBlacklister{
+ /* Colon-separated string list of blacklisted packages, e.g.
+ * "foo.bar.baz;com.fake.name" would blacklist {"foo.bar.baz", "com.fake.name"} packages.
+ *
+ * Blacklisted packages are ignored by iorapd.
+ */
+ server_configurable_flags::GetServerConfigurableFlag(
+ ph_namespace,
+ "iorap_blacklisted_packages",
+ ::android::base::GetProperty("iorapd.blacklist_packages",
+ /*default*/""))
+ };
+
+ LOG(DEBUG) << "RefreshSystemProperties";
+ }
+
+ bool PurgePackage(::android::Printer& printer, const std::string& package_name) {
+ (void)printer;
+ return PurgePackage(package_name);
+ }
+
+ bool PurgePackage(const std::string& package_name) {
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+ db::CleanUpFilesForPackage(db, package_name);
+ LOG(DEBUG) << "PurgePackage: " << package_name;
+ return true;
+ }
+
+ bool CompilePackage(::android::Printer& printer, const std::string& package_name) {
+ (void)printer;
+
+ ScopedFormatTrace atrace_compile_app(ATRACE_TAG_PACKAGE_MANAGER,
+ "Compile one app on device");
+
+ maintenance::ControllerParameters params{
+ /*output_text*/false,
+ /*inode_textcache*/std::nullopt,
+ WOULD_LOG(VERBOSE),
+ /*recompile*/false,
+ s_min_traces,
+ std::make_shared<maintenance::Exec>()};
+
+ db::DbHandle db{db::SchemaModel::GetSingleton()};
+ bool res = maintenance::CompileSingleAppOnDevice(db, std::move(params), package_name);
+ LOG(DEBUG) << "CompilePackage: " << package_name;
+
+ return res;
+ }
+
+ bool readahead_allowed_{true};
+
perfetto::RxProducerFactory& perfetto_factory_;
bool tracing_allowed_{true};
+ PackageBlacklister package_blacklister_{};
+
+ std::weak_ptr<TaskResultCallbacks> callbacks_; // avoid cycles with weakptr.
+
using AppLaunchEventRefWrapper = AppLaunchEventSubject::RefWrapper;
rxcpp::observable<AppLaunchEventRefWrapper> app_launch_events_;
AppLaunchEventSubject app_launch_event_subject_;
+ AppLaunchEventDefender app_launch_event_defender_;
+
+ rxcpp::observable<std::pair<RequestId, JobScheduledEvent>> job_scheduled_events_;
+ JobScheduledEventSubject job_scheduled_event_subject_;
rxcpp::observable<RequestId> completed_requests_;
@@ -456,8 +1319,14 @@ class EventManager::Impl {
observe_on_one_worker worker_thread2_;
// low priority idle-class thread for IO operations.
observe_on_one_worker io_thread_;
+ // async futures pool for async rx operations.
+ AsyncPool async_pool_;
+
+ rxcpp::composite_subscription rx_lifetime_; // app launch events
+ rxcpp::composite_subscription rx_lifetime_jobs_; // job scheduled events
- rxcpp::composite_subscription rx_lifetime_;
+ // package version map
+ std::shared_ptr<binder::PackageVersionMap> version_map_;
//INTENTIONAL_COMPILER_ERROR_HERE:
// FIXME:
@@ -488,9 +1357,47 @@ std::shared_ptr<EventManager> EventManager::Create(perfetto::RxProducerFactory&
return p;
}
+void EventManager::SetTaskResultCallbacks(std::shared_ptr<TaskResultCallbacks> callbacks) {
+ return impl_->SetTaskResultCallbacks(std::move(callbacks));
+}
+
+void EventManager::Join() {
+ return impl_->Join();
+}
+
bool EventManager::OnAppLaunchEvent(RequestId request_id,
const AppLaunchEvent& event) {
return impl_->OnAppLaunchEvent(request_id, event);
}
+bool EventManager::OnDexOptEvent(RequestId request_id,
+ const DexOptEvent& event) {
+ return impl_->OnDexOptEvent(request_id, event);
+}
+
+bool EventManager::OnJobScheduledEvent(RequestId request_id,
+ const JobScheduledEvent& event) {
+ return impl_->OnJobScheduledEvent(request_id, event);
+}
+
+bool EventManager::OnPackageChanged(const android::content::pm::PackageChangeEvent& event) {
+ return impl_->OnPackageChanged(event);
+}
+
+void EventManager::Dump(/*borrow*/::android::Printer& printer) {
+ return impl_->Dump(printer);
+}
+
+void EventManager::RefreshSystemProperties(::android::Printer& printer) {
+ return impl_->RefreshSystemProperties(printer);
+}
+
+bool EventManager::PurgePackage(::android::Printer& printer, const std::string& package_name) {
+ return impl_->PurgePackage(printer, package_name);
+}
+
+bool EventManager::CompilePackage(::android::Printer& printer, const std::string& package_name) {
+ return impl_->CompilePackage(printer, package_name);
+}
+
} // namespace iorap::manager
diff --git a/src/manager/event_manager.h b/src/manager/event_manager.h
index 5355296..fa09eec 100644
--- a/src/manager/event_manager.h
+++ b/src/manager/event_manager.h
@@ -18,21 +18,50 @@
#define IORAP_MANAGER_EVENT_MANAGER_H_
#include "binder/app_launch_event.h"
+#include "binder/dexopt_event.h"
+#include "binder/job_scheduled_event.h"
#include "binder/request_id.h"
+#include "binder/task_result.h"
+
+#include <android/content/pm/PackageChangeEvent.h>
#include <memory>
+namespace android {
+class Printer;
+} // namespace android
+
namespace iorap::perfetto {
- struct RxProducerFactory;
+struct RxProducerFactory;
} // namespace iorap::perfetto
namespace iorap::manager {
+// These callbacks are invoked by the EventManager to provide asynchronous notification for the status
+// of an event handler.
+//
+// Calling 'On_Event' in EventManager should be considered merely to start the task.
+// Calling 'OnComplete' here is considered to terminate the request (either with a success or error).
+// OnProgress is optional, but if it is called it must be called prior to 'OnComplete'.
+//
+// All callbacks for the same request-id are sequentially consistent.
+class TaskResultCallbacks {
+ public:
+ virtual void OnProgress(iorap::binder::RequestId request_id, iorap::binder::TaskResult task_result) {}
+ virtual void OnComplete(iorap::binder::RequestId request_id, iorap::binder::TaskResult task_result) {}
+
+ virtual ~TaskResultCallbacks() {}
+};
+
class EventManager {
public:
static std::shared_ptr<EventManager> Create();
static std::shared_ptr<EventManager> Create(
/*borrow*/perfetto::RxProducerFactory& perfetto_factory);
+ void SetTaskResultCallbacks(std::shared_ptr<TaskResultCallbacks> callbacks);
+
+ // Joins any background threads created by EventManager.
+ void Join();
// Handles an AppLaunchEvent:
//
@@ -45,6 +74,41 @@ class EventManager {
bool OnAppLaunchEvent(binder::RequestId request_id,
const binder::AppLaunchEvent& event);
+ // Handles a DexOptEvent:
+ //
+ // Clean up the invalidate traces after package is updated by dexopt.
+ bool OnDexOptEvent(binder::RequestId request_id,
+ const binder::DexOptEvent& event);
+
+
+ // Handles a JobScheduledEvent:
+ //
+ // * Start/stop background jobs (typically for idle maintenance).
+ // * For example, this could kick off a background compiler.
+ bool OnJobScheduledEvent(binder::RequestId request_id,
+ const binder::JobScheduledEvent& event);
+
+ // Handles a PackageChangeEvent:
+ //
+ // * The package manager service send this event for package install
+ // update or delete.
+ bool OnPackageChanged(const android::content::pm::PackageChangeEvent& event);
+
+ // Print to adb shell dumpsys (for bugreport info).
+ void Dump(/*borrow*/::android::Printer& printer);
+
+ // A dumpsys --refresh-properties command signaling that we should
+ // refresh our system properties.
+ void RefreshSystemProperties(::android::Printer& printer);
+
+ // A dumpsys --purge-package <name> command signaling
+ // that all db rows and files associated with a package should be deleted.
+ bool PurgePackage(::android::Printer& printer, const std::string& package_name);
+
+ // A dumpsys --compile-package <name> command signaling
+ // that a package should be recompiled.
+ bool CompilePackage(::android::Printer& printer, const std::string& package_name);
+
class Impl;
private:
std::unique_ptr<Impl> impl_;
diff --git a/src/perfetto/perfetto_consumer.cc b/src/perfetto/perfetto_consumer.cc
new file mode 100644
index 0000000..4fe677b
--- /dev/null
+++ b/src/perfetto/perfetto_consumer.cc
@@ -0,0 +1,608 @@
+// 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/perfetto_consumer.h"
+
+#include "common/trace.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <utils/Looper.h>
+#include <utils/Printer.h>
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <sstream>
+#include <vector>
+
+#include <inttypes.h>
+#include <time.h>
+
+namespace iorap::perfetto {
+
+using State = PerfettoConsumer::State;
+using Handle = PerfettoConsumer::Handle;
+static constexpr Handle kInvalidHandle = PerfettoConsumer::kInvalidHandle;
+using OnStateChangedCb = PerfettoConsumer::OnStateChangedCb;
+using TraceBuffer = PerfettoConsumer::TraceBuffer;
+
+enum class StateKind {
+ kUncreated,
+ kCreated,
+ kStartedTracing,
+ kReadTracing,
+ kTimedOutDestroyed, // same as kDestroyed but timed out.
+ kDestroyed, // calling kDestroyed before timing out.
+};
+
+std::ostream& operator<<(std::ostream& os, StateKind kind) {
+ switch (kind) {
+ case StateKind::kUncreated:
+ os << "kUncreated";
+ break;
+ case StateKind::kCreated:
+ os << "kCreated";
+ break;
+ case StateKind::kStartedTracing:
+ os << "kStartedTracing";
+ break;
+ case StateKind::kReadTracing:
+ os << "kReadTracing";
+ break;
+ case StateKind::kTimedOutDestroyed:
+ os << "kTimedOutDestroyed";
+ break;
+ case StateKind::kDestroyed:
+ os << "kDestroyed";
+ break;
+ default:
+ os << "(invalid)";
+ break;
+ }
+ return os;
+}
+
+std::string ToString(StateKind kind) {
+ std::stringstream ss;
+ ss << kind;
+ return ss.str();
+}
+
+static constexpr uint64_t kSecToNano = 1000000000LL;
+
+static uint64_t GetTimeNanoseconds() {
+ struct timespec now;
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ uint64_t now_ns = (now.tv_sec * kSecToNano + now.tv_nsec);
+ return now_ns;
+}
+
+// Describe the state of our handle in detail for debugging/logging.
+struct HandleDescription {
+ Handle handle_;
+ StateKind kind_{StateKind::kUncreated}; // Our state. required for correctness.
+ OnStateChangedCb callback_{nullptr}; // Required for Destroy callbacks.
+ void* callback_arg_{nullptr};
+
+ // For dumping to logs:
+ State state_{State::kSessionNotFound}; // perfetto state
+ std::optional<uint64_t> started_tracing_ns_; // when StartedTracing last called.
+ std::optional<uint64_t> read_trace_ns_; // When ReadTrace last called.
+ std::uint64_t last_transition_ns_{0};
+ std::optional<uint64_t> trace_cookie_; // atrace beginning at StartTracing.
+ bool trace_ended_{false}; // atrace ending at ReadTrace or Destroy.
+
+ HandleDescription(Handle handle): handle_(handle) {}
+};
+
+// pimpl idiom to hide the implementation details from header
+//
+// Track and verify that our perfetto usage is sane.
+struct PerfettoConsumerImpl::Impl {
+ Impl() : raw_{new PerfettoConsumerRawImpl{}},
+ message_handler_{new TraceMessageHandler{this}} {
+ std::thread watchdog_thread{ [this]() {
+ ::android::sp<::android::Looper> looper;
+ {
+ std::lock_guard<std::mutex> guard{looper_mutex_};
+ looper = ::android::Looper::prepare(/*opts*/0);
+ looper_ = looper;
+ }
+
+ static constexpr int kTimeoutMillis = std::numeric_limits<int>::max();
+
+ while (true) {
+ // Execute any pending callbacks, otherwise just block forever.
+ int result = looper->pollAll(kTimeoutMillis);
+
+ if (result == ::android::Looper::POLL_ERROR) {
+ LOG(ERROR) << "PerfettoConsumerImpl::Looper got a POLL_ERROR";
+ } else {
+ LOG(DEBUG) << "PerfettoConsumerImpl::Looper result was " << result;
+ }
+ }
+ }};
+
+ // Let thread run freely on its own.
+ watchdog_thread.detach();
+
+ // Block until looper_ is prepared.
+ while (true) {
+ std::lock_guard<std::mutex> guard{looper_mutex_};
+ if (looper_ != nullptr) {
+ break;
+ }
+ }
+ }
+
+ private:
+ std::unique_ptr<PerfettoConsumerRawImpl> raw_;
+ std::map<Handle, HandleDescription> states_;
+
+ // We need this to be a counter to avoid memory leaks.
+ Handle last_created_{0};
+ Handle last_destroyed_{0};
+ uint64_t trace_cookie_{0};
+
+ std::mutex mutex_; // Guard above values.
+
+ ::android::sp<::android::Looper> looper_;
+ std::mutex looper_mutex_; // Guard looper_.
+
+ struct TraceMessageHandler : public ::android::MessageHandler {
+ TraceMessageHandler(Impl* impl) : impl_{impl} {
+ CHECK(impl != nullptr);
+ }
+
+ Impl* impl_;
+
+ virtual void handleMessage(const ::android::Message& message) override {
+ impl_->OnTraceMessage(static_cast<Handle>(message.what));
+ }
+ };
+
+ ::android::sp<TraceMessageHandler> message_handler_;
+
+ public:
+ Handle Create(const void* config_proto,
+ size_t config_len,
+ OnStateChangedCb callback,
+ void* callback_arg) {
+ LOG(VERBOSE) << "PerfettoConsumer::Create("
+ << "config_len=" << config_len << ")";
+ Handle handle = raw_->Create(config_proto, config_len, callback, callback_arg);
+
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ // Assume every Handle starts at 0 and then increments by 1 every Create.
+ ++last_created_;
+ CHECK_EQ(last_created_, handle) << "perfetto handle had unexpected behavior.";
+ // Without this^ increment-by-1 behavior our detection of untracked state values is broken.
+ // If we have to, we can go with Untracked=Uncreated|Destroyed but it's better to distinguish
+ // the two if possible.
+
+ HandleDescription handle_desc{handle};
+ handle_desc.handle_ = handle;
+ handle_desc.callback_ = callback;
+ handle_desc.callback_arg_ = callback_arg;
+ UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kCreated);
+
+ // assume we never wrap around due to using int64
+ bool inserted = states_.insert({handle, handle_desc}).second;
+ CHECK(inserted) << "perfetto handle was re-used: " << handle;
+
+ return handle;
+ }
+
+ void StartTracing(Handle handle) {
+ LOG(DEBUG) << "PerfettoConsumer::StartTracing(handle=" << handle << ")";
+
+ uint64_t trace_cookie;
+ {
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ auto it = states_.find(handle);
+ if (it == states_.end()) {
+ LOG(ERROR) << "Cannot StartTracing(" << handle << "), untracked handle";
+ return;
+ }
+ HandleDescription& handle_desc = it->second;
+
+ raw_->StartTracing(handle);
+ UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kStartedTracing);
+ }
+
+ // Use a looper here to add a timeout and immediately destroy the trace buffer.
+ CHECK_LE(static_cast<int64_t>(handle), static_cast<int64_t>(std::numeric_limits<int>::max()));
+ int message_code = static_cast<int>(handle);
+ ::android::Message message{message_code};
+
+ std::lock_guard<std::mutex> looper_guard{looper_mutex_};
+ looper_->sendMessageDelayed(static_cast<nsecs_t>(GetPropertyTraceTimeoutNs()),
+ message_handler_,
+ message);
+ }
+
+ TraceBuffer ReadTrace(Handle handle) {
+ LOG(DEBUG) << "PerfettoConsumer::ReadTrace(handle=" << handle << ")";
+
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ auto it = states_.find(handle);
+ if (it == states_.end()) {
+ LOG(ERROR) << "Cannot ReadTrace(" << handle << "), untracked handle";
+ return TraceBuffer{};
+ }
+
+ HandleDescription& handle_desc = it->second;
+
+ TraceBuffer trace_buffer = raw_->ReadTrace(handle);
+ UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kReadTracing);
+
+ return trace_buffer;
+ }
+
+ void Destroy(Handle handle) {
+ HandleDescription handle_desc{handle};
+ TryDestroy(handle, /*do_destroy*/true, /*out*/&handle_desc);;
+ }
+
+ bool TryDestroy(Handle handle, bool do_destroy, /*out*/HandleDescription* handle_desc_out) {
+ CHECK(handle_desc_out != nullptr);
+
+ LOG(VERBOSE) << "PerfettoConsumer::Destroy(handle=" << handle << ")";
+
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ auto it = states_.find(handle);
+ if (it == states_.end()) {
+ // Leniency for calling Destroy multiple times. It's not a mistake.
+ LOG(ERROR) << "Cannot Destroy(" << handle << "), untracked handle";
+ return false;
+ }
+
+ HandleDescription& handle_desc = it->second;
+
+ if (do_destroy) {
+ raw_->Destroy(handle);
+ }
+ UpdateHandleDescription(/*inout*/&handle_desc, StateKind::kDestroyed);
+
+ *handle_desc_out = handle_desc;
+
+ // No longer track this handle to avoid memory leaks.
+ last_destroyed_ = handle;
+ states_.erase(it);
+
+ return true;
+ }
+
+ State PollState(Handle handle) {
+ // Just pass-through the call, we never use it directly anyway.
+ return raw_->PollState(handle);
+ }
+
+ // Either fetch or infer the current handle state from a handle.
+ // Meant for debugging/logging only.
+ HandleDescription GetOrInferHandleDescription(Handle handle) {
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ auto it = states_.find(handle);
+ if (it == states_.end()) {
+ HandleDescription state{handle};
+ // If it's untracked it hasn't been created yet, or it was already destroyed.
+ if (IsDestroyed(handle)) {
+ UpdateHandleDescription(/*inout*/&state, StateKind::kDestroyed);
+ } else {
+ if (!IsUncreated(handle)) {
+ LOG(WARNING) << "bad state detection";
+ }
+ UpdateHandleDescription(/*inout*/&state, StateKind::kUncreated);
+ }
+ return state;
+ }
+ return it->second;
+ }
+
+ void OnTraceMessage(Handle handle) {
+ LOG(VERBOSE) << "OnTraceMessage(" << static_cast<int64_t>(handle) << ")";
+ HandleDescription handle_desc{handle};
+ {
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ auto it = states_.find(handle);
+ if (it == states_.end()) {
+ // Handle values are never re-used, so we can simply ignore the message here
+ // instead of having to remove it from the message queue.
+ LOG(VERBOSE) << "OnTraceMessage(" << static_cast<int64_t>(handle)
+ << ") no longer tracked handle";
+ return;
+ }
+ handle_desc = it->second;
+ }
+
+ // First check. Has this trace been active for too long?
+ uint64_t now_ns = GetTimeNanoseconds();
+ if (handle_desc.kind_ == StateKind::kStartedTracing) {
+ // Ignore other kinds of traces because they don't exhaust perfetto resources.
+ CHECK(handle_desc.started_tracing_ns_.has_value()) << static_cast<int64_t>(handle);
+
+ uint64_t started_tracing_ns = *handle_desc.started_tracing_ns_;
+
+ if ((now_ns - started_tracing_ns) > GetPropertyTraceTimeoutNs()) {
+ LOG(WARNING) << "Perfetto Handle timed out after " << (now_ns - started_tracing_ns) << "ns"
+ << ", forcibly destroying";
+
+ // Let the callback handler call Destroy.
+ handle_desc.callback_(handle, State::kTraceFailed, handle_desc.callback_arg_);
+ }
+ }
+
+ // Second check. Are there too many traces now? Cull the old traces.
+ std::vector<HandleDescription> handle_list;
+ do {
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ size_t max_trace_count = GetPropertyMaxTraceCount();
+ if (states_.size() > max_trace_count) {
+ size_t overflow_count = states_.size() - max_trace_count;
+ LOG(WARNING) << "Too many perfetto handles, overflowed by " << overflow_count
+ << ", pruning down to " << max_trace_count;
+ } else {
+ break;
+ }
+
+ size_t prune_count = states_.size() - max_trace_count;
+ auto it = states_.begin();
+ for (size_t i = 0; i < prune_count; ++i) {
+ // Simply prune by handle 1,2,3,4...
+ // We could do better with a timestamp if we wanted to.
+ ++it;
+ handle_list.push_back(it->second);
+ }
+ } while (false);
+
+ for (HandleDescription& handle_desc : handle_list) {
+ LOG(DEBUG) << "Perfetto handle pruned: " << static_cast<int64_t>(handle);
+
+ // Let the callback handler call Destroy.
+ handle_desc.callback_(handle, State::kTraceFailed, handle_desc.callback_arg_);
+ }
+ }
+
+ private:
+ static uint64_t GetPropertyTraceTimeoutNs() {
+ static uint64_t value = // property is timeout in seconds
+ ::android::base::GetUintProperty<uint64_t>("iorapd.perfetto.timeout", /*default*/10);
+ return value * kSecToNano;
+ }
+
+ static size_t GetPropertyMaxTraceCount() {
+ static size_t value =
+ ::android::base::GetUintProperty<size_t>("iorapd.perfetto.max_traces", /*default*/5);
+ return value;
+ }
+
+ void UpdateHandleDescription(/*inout*/HandleDescription* handle_desc, StateKind kind) {
+ CHECK(handle_desc != nullptr);
+ handle_desc->kind_ = kind;
+ handle_desc->state_ = raw_->PollState(handle_desc->handle_);
+
+ handle_desc->last_transition_ns_ = GetTimeNanoseconds();
+ if (kind == StateKind::kStartedTracing) {
+ if (!handle_desc->started_tracing_ns_) {
+ handle_desc->started_tracing_ns_ = handle_desc->last_transition_ns_;
+
+ handle_desc->trace_cookie_ = ++trace_cookie_;
+
+ atrace_async_begin(ATRACE_TAG_ACTIVITY_MANAGER,
+ "Perfetto Scoped Trace",
+ *handle_desc->trace_cookie_);
+ atrace_int(ATRACE_TAG_ACTIVITY_MANAGER,
+ "Perfetto::Trace Handle",
+ static_cast<int32_t>(handle_desc->handle_));
+ }
+ }
+
+ if (kind == StateKind::kReadTracing) {
+ if (!handle_desc->read_trace_ns_) {
+ handle_desc->read_trace_ns_ = handle_desc->last_transition_ns_;
+
+ if (handle_desc->trace_cookie_.has_value() && !handle_desc->trace_ended_) {
+ atrace_async_end(ATRACE_TAG_ACTIVITY_MANAGER,
+ "Perfetto Scoped Trace",
+ handle_desc->trace_cookie_.value());
+
+ handle_desc->trace_ended_ = true;
+ }
+ }
+ }
+
+ // If Destroy is called prior to ReadTrace, mark the atrace as finished.
+ if (kind == StateKind::kDestroyed && handle_desc->trace_cookie_ && !handle_desc->trace_ended_) {
+ atrace_async_end(ATRACE_TAG_ACTIVITY_MANAGER,
+ "Perfetto Scoped Trace",
+ *handle_desc->trace_cookie_);
+ handle_desc->trace_ended_ = true;
+ }
+ }
+
+ // The following state detection is for debugging only.
+ // We figure out if something is destroyed, uncreated, or live.
+
+ // Does not distinguish between kTimedOutDestroyed and kDestroyed.
+ bool IsDestroyed(Handle handle) const {
+ auto it = states_.find(handle);
+ if (it != states_.end()) {
+ // Tracked values are not destroyed yet.
+ return false;
+ }
+
+ if (handle == kInvalidHandle) {
+ return false;
+ }
+
+ // The following assumes handles are incrementally generated:
+ if (it == states_.end()) {
+ // value is in range of [0, last_destroyed] => destroyed.
+ return handle <= last_destroyed_;
+ }
+
+ auto min_it = states_.begin();
+ if (handle < min_it->first) {
+ // value smaller than anything tracked: it was destroyed and we stopped tracking it.
+ return true;
+ }
+
+ auto max_it = states_.rbegin();
+ if (handle > max_it->first) {
+ // value too big: it's uncreated;
+ return false;
+ }
+
+ // else it was a value that was previously tracked within [min,max] but no longer
+ return true;
+ }
+
+ bool IsUncreated(Handle handle) const {
+ auto it = states_.find(handle);
+ if (it != states_.end()) {
+ // Tracked values are not uncreated.
+ return false;
+ }
+
+ if (handle == kInvalidHandle) {
+ // Strangely enough, an invalid handle can never be created.
+ return true;
+ }
+
+ // The following assumes handles are incrementally generated:
+ if (it == states_.end()) {
+ // value is in range of (last_destroyed, inf) => uncreated.
+ return handle > last_destroyed_;
+ }
+
+ auto min_it = states_.begin();
+ if (handle < min_it->first) {
+ // value smaller than anything tracked: it was destroyed and we stopped tracking it.
+ return false;
+ }
+
+ auto max_it = states_.rbegin();
+ if (handle > max_it->first) {
+ // value too big: it's uncreated;
+ return true;
+ }
+
+ // else it was a value that was previously tracked within [min,max] but no longer
+ return false;
+ }
+
+ public:
+ void Dump(::android::Printer& printer) {
+ // Locking can fail if we dump during a deadlock, so just do a best-effort lock here.
+ bool is_it_locked = mutex_.try_lock();
+
+ printer.printFormatLine("Perfetto consumer state:");
+ if (!is_it_locked) {
+ printer.printLine(""""" (possible deadlock)");
+ }
+ printer.printFormatLine(" Last destroyed handle: %" PRId64, last_destroyed_);
+ printer.printFormatLine(" Last created handle: %" PRId64, last_created_);
+ printer.printFormatLine("");
+ printer.printFormatLine(" In-flight handles:");
+
+ for (auto it = states_.begin(); it != states_.end(); ++it) {
+ HandleDescription& handle_desc = it->second;
+ uint64_t started_tracing =
+ handle_desc.started_tracing_ns_ ? *handle_desc.started_tracing_ns_ : 0;
+ printer.printFormatLine(" Handle %" PRId64, handle_desc.handle_);
+ printer.printFormatLine(" Kind: %s", ToString(handle_desc.kind_).c_str());
+ printer.printFormatLine(" Perfetto State: %d", static_cast<int>(handle_desc.state_));
+ printer.printFormatLine(" Started tracing at: %" PRIu64, started_tracing);
+ printer.printFormatLine(" Last transition at: %" PRIu64,
+ handle_desc.last_transition_ns_);
+ }
+ if (states_.empty()) {
+ printer.printFormatLine(" (None)");
+ }
+
+ printer.printFormatLine("");
+
+ if (is_it_locked) { // u.b. if calling unlock on an unlocked mutex.
+ mutex_.unlock();
+ }
+ }
+
+ static PerfettoConsumerImpl::Impl* GetImplSingleton() {
+ static PerfettoConsumerImpl::Impl impl;
+ return &impl;
+ }
+};
+
+// Use a singleton because fruit instantiates a new PerfettoConsumer object for every
+// new rx chain in RxProducerFactory. However, we want to track all perfetto transitions globally
+// through 1 impl object.
+//
+// TODO: Avoiding a singleton would mean a more significant refactoring to remove the fruit/perfetto
+// usage.
+
+
+//
+// Forward all calls to PerfettoConsumerImpl::Impl
+//
+
+PerfettoConsumerImpl::~PerfettoConsumerImpl() {
+ // delete impl_; // TODO: no singleton
+}
+
+void PerfettoConsumerImpl::Initialize() {
+ // impl_ = new PerfettoConsumerImpl::Impl(); // TODO: no singleton
+ impl_ = PerfettoConsumerImpl::Impl::GetImplSingleton();
+}
+
+void PerfettoConsumerImpl::Dump(::android::Printer& printer) {
+ PerfettoConsumerImpl::Impl::GetImplSingleton()->Dump(/*borrow*/printer);
+}
+
+PerfettoConsumer::Handle PerfettoConsumerImpl::Create(const void* config_proto,
+ size_t config_len,
+ PerfettoConsumer::OnStateChangedCb callback,
+ void* callback_arg) {
+ return impl_->Create(config_proto,
+ config_len,
+ callback,
+ callback_arg);
+}
+
+void PerfettoConsumerImpl::StartTracing(PerfettoConsumer::Handle handle) {
+ impl_->StartTracing(handle);
+}
+
+PerfettoConsumer::TraceBuffer PerfettoConsumerImpl::ReadTrace(PerfettoConsumer::Handle handle) {
+ return impl_->ReadTrace(handle);
+}
+
+void PerfettoConsumerImpl::Destroy(PerfettoConsumer::Handle handle) {
+ impl_->Destroy(handle);
+}
+
+PerfettoConsumer::State PerfettoConsumerImpl::PollState(PerfettoConsumer::Handle handle) {
+ return impl_->PollState(handle);
+}
+
+} // namespace iorap::perfetto
diff --git a/src/perfetto/perfetto_consumer.h b/src/perfetto/perfetto_consumer.h
index ce04ffd..2d828c0 100644
--- a/src/perfetto/perfetto_consumer.h
+++ b/src/perfetto/perfetto_consumer.h
@@ -18,6 +18,10 @@
#include <fruit/fruit.h>
#include <perfetto/public/consumer_api.h> // libperfetto
+namespace android {
+class Printer;
+} // namespace android
+
namespace iorap::perfetto {
// Abstract out the Perfetto C API behind a virtual interface:
@@ -48,9 +52,9 @@ struct PerfettoConsumer {
};
// "Live" implementation that calls down to libperfetto.
-struct PerfettoConsumerImpl : public PerfettoConsumer {
+struct PerfettoConsumerRawImpl : public PerfettoConsumer {
// Marks this constructor as the one to use for injection.
- INJECT(PerfettoConsumerImpl()) = default;
+ INJECT(PerfettoConsumerRawImpl()) = default;
virtual Handle Create(const void* config_proto,
size_t config_len,
@@ -77,7 +81,32 @@ struct PerfettoConsumerImpl : public PerfettoConsumer {
return ::perfetto::consumer::PollState(handle);
}
- virtual ~PerfettoConsumerImpl() {}
+ virtual ~PerfettoConsumerRawImpl() {}
+};
+
+// "Safe" implementation that has extra checking around it.
+class PerfettoConsumerImpl : public PerfettoConsumer {
+ public:
+ // Marks this constructor as the one to use for injection.
+ INJECT(PerfettoConsumerImpl()) { Initialize(); }
+
+ virtual Handle Create(const void* config_proto,
+ size_t config_len,
+ OnStateChangedCb callback,
+ void* callback_arg) override;
+ virtual void StartTracing(Handle handle) override;
+ virtual TraceBuffer ReadTrace(Handle handle) override;
+ virtual void Destroy(Handle handle) override;
+ virtual State PollState(Handle handle) override;
+
+ virtual ~PerfettoConsumerImpl();
+
+ static void Dump(/*borrow*/::android::Printer& printer);
+
+ private:
+ void Initialize();
+ struct Impl;
+ PerfettoConsumerImpl::Impl* impl_;
};
} // namespace iorap::perfetto
diff --git a/src/perfetto/rx_producer.cc b/src/perfetto/rx_producer.cc
index 14311a1..824600c 100644
--- a/src/perfetto/rx_producer.cc
+++ b/src/perfetto/rx_producer.cc
@@ -83,7 +83,7 @@ PerfettoDependencies::Component PerfettoDependencies::CreateComponent() {
.bind<PerfettoConsumer, PerfettoConsumerImpl>()
.registerProvider([]() /* -> TraceConfig */ {
return CreateConfig(kTraceDurationMs,
- /*deferred_start*/true,
+ /*deferred_start*/false,
kBufferSize);
});
}
@@ -199,6 +199,12 @@ class StateChangedSubject {
using State = ::perfetto::consumer::State;
using Handle = ::perfetto::consumer::Handle;
+ // Static members to solve use-after-free bug.
+ // The object is accessed from not only perfetto thread, but also iorap
+ // thread. Use this global map to manage it.
+ static std::mutex state_subject_mutex_;
+ static std::unordered_map<Handle, StateChangedSubject*> state_subject_map_;
+
StateChangedSubject(const ::perfetto::protos::TraceConfig& trace_config,
rxcpp::subscriber<PerfettoStateChange> destination,
std::shared_ptr<PerfettoConsumer> perfetto_consumer)
@@ -391,7 +397,9 @@ class StateChangedSubject {
bound_.store(true); // seq_cst release.
}
- // Thread safety: Called by libperfetto background thread (same one every time).
+
+ // Called by libperfetto background thread (same one every time) and iorap
+ // thread.
static void CallbackOnStateChanged(Handle handle, State state, void* callback_arg) {
LOG(VERBOSE) << "CallbackOnStateChanged(handle=" << handle << ",state=" << state
<< ",callback_arg=" << callback_arg << ")";
@@ -399,24 +407,30 @@ class StateChangedSubject {
// Validate OnStateChanged callback invariants, guaranteed by libperfetto.
DCHECK_NE(handle, ::perfetto::consumer::kInvalidHandle);
- // Note: Perfetto guarantees this callback always occurs on the same thread,
- // so we don't need to do any extra thread synchronization here since we are only mutating
- // StateChangedSubject from within this function.
-
// TODO: the memory ordering guarantees should be explicitly specified in consumer_api.h:
// This isn't specific enough:
// "The callback will be invoked on an internal thread and must not block."
// However looking at the implementation it posts onto a single-thread task runner,
// so this must be the case.
- StateChangedSubject* state_subject = reinterpret_cast<StateChangedSubject*>(callback_arg);
// This current thread owns 'StateChangedSubject', no other threads must access it.
// Explicit synchronization is not necessary.
- if (!state_subject->OnStateChanged(handle, state)) {
- // Clean up the state tracker when we reach a terminal state.
- // This means that no future callbacks will occur anymore.
- delete state_subject;
+ {
+ std::lock_guard<std::mutex> guard(StateChangedSubject::state_subject_mutex_);
+ auto it = StateChangedSubject::state_subject_map_.find(handle);
+ // If the object is already deleted, do nothing.
+ if (it == StateChangedSubject::state_subject_map_.end()) {
+ return;
+ }
+
+ StateChangedSubject* state_subject = it->second;
+ if (!state_subject->OnStateChanged(handle, state)) {
+ // Clean up the state tracker when we reach a terminal state.
+ // This means that no future callbacks will occur anymore.
+ StateChangedSubject::state_subject_map_.erase(it);
+ delete state_subject;
+ }
}
}
@@ -447,6 +461,10 @@ class StateChangedSubject {
// of just being subject-like?
};
+std::mutex StateChangedSubject::state_subject_mutex_;
+std::unordered_map<::perfetto::consumer::Handle,
+ StateChangedSubject*> StateChangedSubject::state_subject_map_;
+
// Note: The states will be emitted on a separate thread, so e.g. #as_blocking()
// needs to be used to avoid dropping everything on the floor.
//
@@ -484,6 +502,11 @@ static auto /*[observable<State>, shared_ptr<PerfettoConsumerHandle>]*/
return;
}
+ {
+ std::lock_guard<std::mutex> guard(StateChangedSubject::state_subject_mutex_);
+ StateChangedSubject::state_subject_map_[handle] = state_subject.get();
+ }
+
std::shared_ptr<PerfettoConsumerHandle> safe_handle{
new PerfettoConsumerHandle{perfetto_consumer, handle}};
@@ -567,11 +590,63 @@ bool BinaryWireProtobuf<T>::WriteStringToFd(int fd) const {
return true;
}
+template <typename T>
+std::optional<BinaryWireProtobuf<T>> BinaryWireProtobuf<T>::ReadFullyFromFile(
+ const std::string& path,
+ bool follow_symlinks) {
+ std::vector<std::byte> data;
+
+ int flags = O_RDONLY | O_CLOEXEC | O_BINARY | (follow_symlinks ? 0 : O_NOFOLLOW);
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags)));
+ if (fd == -1) {
+ return std::nullopt;
+ }
+
+ if (ReadFdToString(fd.get(), /*out*/&data)) {
+ return BinaryWireProtobuf<T>{std::move(data)};
+ } else {
+ return std::nullopt;
+ }
+}
+
+template <typename T>
+bool BinaryWireProtobuf<T>::operator==(const BinaryWireProtobuf<T>& other) const {
+ if (data_.size() != other.data_.size()) {
+ return false;
+ }
+ return std::equal(data_.begin(), data_.end(), other.data_.begin());
+}
+
+template <typename T>
+bool BinaryWireProtobuf<T>::ReadFdToString(int fd, /*out*/std::vector<std::byte>* content) {
+ DCHECK(content != nullptr);
+
+ content->clear();
+
+ struct stat sb;
+ if (fstat(fd, /*out*/&sb) != -1 && sb.st_size > 0) {
+ content->reserve(sb.st_size);
+ }
+
+ char buf[BUFSIZ];
+ auto it = content->begin();
+ ssize_t n;
+ while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], sizeof(buf)))) > 0) {
+ content->insert(it,
+ reinterpret_cast<std::byte*>(&buf[0]),
+ reinterpret_cast<std::byte*>(&buf[n]));
+
+ std::advance(/*inout*/it, static_cast<size_t>(n));
+
+ static_assert(sizeof(char) == sizeof(std::byte), "sanity check for reinterpret cast");
+ }
+ return (n == 0) ? true : false;
+}
+
// explicit template instantiation.
template struct BinaryWireProtobuf<::google::protobuf::MessageLite>;
// TODO: refactor this not to need the template instantiation.
-#if defined(__ANDROID__)
// Copy of the 2.6.18 kernel header (linux/ioprio.h)
#define IOPRIO_WHO_PROCESS (1)
@@ -584,7 +659,6 @@ template struct BinaryWireProtobuf<::google::protobuf::MessageLite>;
#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
-#endif
static int ioprio_get(int which, int who) {
return syscall(SYS_ioprio_get, which, who);
diff --git a/src/perfetto/rx_producer.h b/src/perfetto/rx_producer.h
index f4c40b4..4e93f0d 100644
--- a/src/perfetto/rx_producer.h
+++ b/src/perfetto/rx_producer.h
@@ -51,10 +51,39 @@ struct PerfettoDependencies {
uint32_t buffer_size = 4096);
};
+namespace detail {
+ template <typename T>
+ struct concept_message_lite_base {
+ static_assert(std::is_base_of_v<::google::protobuf::MessageLite, T>,
+ "T must inherit from MessageLite");
+ using type = T;
+ };
+
+ template <typename T>
+ using concept_message_lite_base_t = typename concept_message_lite_base<T>::type;
+} // namespace detail
+
+/*
+ * In Android's version of libprotobuf, move-constructors are not generated.
+ * This results in a legitimate (~10sec per TracePacket being compiled) slowdown,
+ * so we need to avoid it everywhere.
+ *
+ * 1) Don't copy the protos, move them instead.
+ * 2) Use 'shared_ptr' because rxcpp won't compile with unique_ptr.
+ */
+template <typename T>
+using ProtobufPtr = std::shared_ptr<detail::concept_message_lite_base_t<const T>>;
+
+template <typename T>
+using ProtobufMutablePtr = std::shared_ptr<detail::concept_message_lite_base_t<T>>;
+
// This acts as a lightweight type marker so that we know what data has actually
// encoded under the hood.
template <typename T>
struct BinaryWireProtobuf {
+ static_assert(std::is_base_of_v<::google::protobuf::MessageLite, T>,
+ "T should be a base class of MessageLite");
+
std::vector<std::byte>& data() {
return data_;
}
@@ -78,13 +107,21 @@ struct BinaryWireProtobuf {
data_.data());
}
+ explicit BinaryWireProtobuf(std::vector<std::byte> data) : data_{std::move(data)} {
+ }
+
+ // You wouldn't want to accidentally copy a giant multi-megabyte chunk would you?
+ // BinaryWireProtobuf(const BinaryWireProtobuf& other) = delete; // FIXME: rx likes to copy.
+ BinaryWireProtobuf(const BinaryWireProtobuf& other) = default;
+ BinaryWireProtobuf(BinaryWireProtobuf&& other) = default;
+
// Important: Deserialization could fail, for example data is truncated or
// some minor disc corruption occurred.
template <typename U>
- std::optional<U> MaybeUnserialize() {
- U unencoded;
+ std::optional<ProtobufPtr<U>> MaybeUnserialize() {
+ ProtobufMutablePtr<U> unencoded{new U{}};
- if (!unencoded.ParseFromArray(data_.data(), data_.size())) {
+ if (!unencoded->ParseFromArray(data_.data(), data_.size())) {
return std::nullopt;
}
@@ -94,10 +131,20 @@ struct BinaryWireProtobuf {
bool WriteFullyToFile(const std::string& path,
bool follow_symlinks = false) const;
+ static std::optional<BinaryWireProtobuf<T>> ReadFullyFromFile(const std::string& path,
+ bool follow_symlinks = false);
+
+ bool operator==(const BinaryWireProtobuf<T>& other) const;
+ bool operator!=(const BinaryWireProtobuf<T>& other) const {
+ return !(*this == other);
+ }
+
private:
static bool CleanUpAfterFailedWrite(const std::string& path);
bool WriteStringToFd(int fd) const;
+ static bool ReadFdToString(int fd, /*out*/std::vector<std::byte>* data);
+
std::vector<std::byte> data_;
};
diff --git a/src/prefetcher/main.cc b/src/prefetcher/main.cc
new file mode 100644
index 0000000..94ba141
--- /dev/null
+++ b/src/prefetcher/main.cc
@@ -0,0 +1,190 @@
+// Copyright (C) 2019 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 "common/debug.h"
+#include "common/loggers.h"
+#include "prefetcher/prefetcher_daemon.h"
+
+#include <android-base/parseint.h>
+#include <android-base/logging.h>
+
+#include <iostream>
+#include <optional>
+#include <string_view>
+#include <string>
+#include <vector>
+
+#include <signal.h>
+
+#if defined(IORAP_PREFETCHER_MAIN)
+
+namespace iorap::prefetcher {
+
+void Usage(char** argv) {
+ std::cerr << "Usage: " << argv[0] << " [--input-fd=#] [--output-fd=#]" << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Run the readahead daemon which can prefetch files given a command." << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Optional flags:" << std::endl;
+ std::cerr << " --help,-h Print this Usage." << std::endl;
+ std::cerr << " --input-fd,-if Input FD (default stdin)." << std::endl;
+ std::cerr << " --output-fd,-of Output FD (default stdout)." << std::endl;
+ std::cerr << " --use-sockets,-us Use AF_UNIX sockets (default off)." << std::endl;
+ std::cerr << " --command-format=[text|binary],-cf (default text)." << std::endl;
+ std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
+ std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
+ exit(1);
+}
+
+int Main(int argc, char** argv) {
+ // Go to system logcat + stderr when running from command line.
+ android::base::InitLogging(argv, iorap::common::StderrAndLogdLogger{android::base::SYSTEM});
+
+ bool wait_for_keystroke = false;
+ bool enable_verbose = false;
+
+ bool command_format_text = false; // false = binary.
+
+ int arg_input_fd = -1;
+ int arg_output_fd = -1;
+
+ std::vector<std::string> arg_input_filenames;
+ bool arg_use_sockets = false;
+
+ LOG(VERBOSE) << "argparse: argc=" << argc;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+ bool has_arg_next = (arg+1)<argc;
+ std::string arg_next = has_arg_next ? argv[arg+1] : "";
+
+ LOG(VERBOSE) << "argparse: argv[" << arg << "]=" << argstr;
+
+ if (argstr == "--help" || argstr == "-h") {
+ Usage(argv);
+ } else if (argstr == "--input-fd" || argstr == "-if") {
+ if (!has_arg_next) {
+ LOG(ERROR) << "--input-fd=<numeric-value>";
+ Usage(argv);
+ }
+ if (!::android::base::ParseInt(arg_next, /*out*/&arg_input_fd)) {
+ LOG(ERROR) << "--input-fd value must be numeric";
+ Usage(argv);
+ }
+ } else if (argstr == "--output-fd" || argstr == "-of") {
+ if (!has_arg_next) {
+ LOG(ERROR) << "--output-fd=<numeric-value>";
+ Usage(argv);
+ }
+ if (!::android::base::ParseInt(arg_next, /*out*/&arg_output_fd)) {
+ LOG(ERROR) << "--output-fd value must be numeric";
+ Usage(argv);
+ }
+ } else if (argstr == "--command-format=" || argstr == "-cf") {
+ if (!has_arg_next) {
+ LOG(ERROR) << "--command-format=text|binary";
+ Usage(argv);
+ }
+ if (arg_next == "text") {
+ command_format_text = true;
+ } else if (arg_next == "binary") {
+ command_format_text = false;
+ } else {
+ LOG(ERROR) << "--command-format must be one of {text,binary}";
+ Usage(argv);
+ }
+ } else if (argstr == "--use-sockets" || argstr == "-us") {
+ arg_use_sockets = true;
+ } else if (argstr == "--verbose" || argstr == "-v") {
+ enable_verbose = true;
+ } else if (argstr == "--wait" || argstr == "-w") {
+ wait_for_keystroke = true;
+ } else {
+ arg_input_filenames.push_back(argstr);
+ }
+ }
+
+ if (enable_verbose) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+ LOG(VERBOSE) << "Verbose check";
+ LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::DEBUG);
+ }
+
+ LOG(VERBOSE) << "argparse: argc=" << argc;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+
+ LOG(VERBOSE) << "argparse: argv[" << arg << "]=" << argstr;
+ }
+
+ // Useful to attach a debugger...
+ // 1) $> iorap.cmd.readahead -w <args>
+ // 2) $> gdbclient <pid>
+ if (wait_for_keystroke) {
+ LOG(INFO) << "Self pid: " << getpid();
+
+ raise(SIGSTOP);
+ // LOG(INFO) << "Press any key to continue...";
+ // std::cin >> wait_for_keystroke;
+ }
+
+ // auto system_call = std::make_unique<SystemCallImpl>();
+ // TODO: mock readahead calls?
+ //
+ // Uncomment this if we want to leave the process around to inspect it from adb shell.
+ // sleep(100000);
+
+ int return_code = 0;
+
+ LOG(VERBOSE) << "Hello world";
+
+ if (arg_input_fd == -1) {
+ arg_input_fd = STDIN_FILENO;
+ }
+ if (arg_output_fd == -1) {
+ arg_output_fd = STDOUT_FILENO;
+ }
+
+ PrefetcherForkParameters params{};
+ params.input_fd = arg_input_fd;
+ params.output_fd = arg_output_fd;
+ params.format_text = command_format_text;
+ params.use_sockets = arg_use_sockets;
+
+ LOG(VERBOSE) << "main: Starting PrefetcherDaemon: "
+ << "input_fd=" << params.input_fd
+ << ",output_fd=" << params.output_fd;
+ {
+ PrefetcherDaemon daemon;
+ // Blocks until receiving an exit command.
+ daemon.Main(std::move(params));
+ }
+ LOG(VERBOSE) << "main: Terminating";
+
+ // 0 -> successfully executed all commands.
+ // 1 -> failed along the way (#on_error and also see the error logs).
+ return return_code;
+}
+
+} // namespace iorap::prefetcher
+
+int main(int argc, char** argv) {
+ return ::iorap::prefetcher::Main(argc, argv);
+}
+
+#endif // IORAP_PREFETCHER_MAIN
diff --git a/src/prefetcher/main_client.cc b/src/prefetcher/main_client.cc
new file mode 100644
index 0000000..ff7ee6d
--- /dev/null
+++ b/src/prefetcher/main_client.cc
@@ -0,0 +1,160 @@
+// Copyright (C) 2019 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 "common/debug.h"
+#include "prefetcher/read_ahead.h"
+#include "prefetcher/task_id.h"
+
+#include <android-base/parseint.h>
+#include <android-base/logging.h>
+
+#include <iostream>
+#include <optional>
+#include <string_view>
+#include <string>
+#include <vector>
+
+#include <signal.h>
+#include <unistd.h>
+
+
+namespace iorap::prefetcher {
+
+static void UsageClient(char** argv) {
+ std::cerr << "UsageClient: " << argv[0] << " <path-to-compiled-trace.pb> [... pathN]" << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Run the readahead daemon which can prefetch files given a command." << std::endl;
+ std::cerr << "" << std::endl;
+ std::cerr << " Optional flags:" << std::endl;
+ std::cerr << " --help,-h Print this UsageClient." << std::endl;
+ std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl;
+ std::cerr << " --task-duration-ms,-tdm Set task duration (default: 0ms)." << std::endl;
+ std::cerr << " --use-sockets,-us Use AF_UNIX sockets (default: off)" << std::endl;
+ std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl;
+ exit(1);
+}
+
+int MainClient(int argc, char** argv) {
+ android::base::InitLogging(argv);
+ android::base::SetLogger(android::base::StderrLogger);
+
+ bool wait_for_keystroke = false;
+ bool enable_verbose = false;
+
+ bool command_format_text = false; // false = binary.
+
+ unsigned int arg_task_duration_ms = 10000;
+ std::vector<std::string> arg_input_filenames;
+ bool arg_use_sockets = false;
+
+ LOG(VERBOSE) << "argparse: argc=" << argc;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+ bool has_arg_next = (arg+1)<argc;
+ std::string arg_next = has_arg_next ? argv[arg+1] : "";
+
+ LOG(VERBOSE) << "argparse: argv[" << arg << "]=" << argstr;
+
+ if (argstr == "--help" || argstr == "-h") {
+ UsageClient(argv);
+ } else if (argstr == "--use-sockets" || argstr == "-us") {
+ arg_use_sockets = true;
+ } else if (argstr == "--verbose" || argstr == "-v") {
+ enable_verbose = true;
+ } else if (argstr == "--wait" || argstr == "-w") {
+ wait_for_keystroke = true;
+ } else if (argstr == "--task-duration-ms" || argstr == "-tdm") {
+ if (!has_arg_next) {
+ LOG(ERROR) << "--task-duration-ms: requires uint parameter";
+ UsageClient(argv);
+ } else if (!::android::base::ParseUint(arg_next, &arg_task_duration_ms)) {
+ LOG(ERROR) << "--task-duration-ms: requires non-negative parameter";
+ UsageClient(argv);
+ }
+ } else {
+ arg_input_filenames.push_back(argstr);
+ }
+ }
+
+ if (enable_verbose) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+ LOG(VERBOSE) << "Verbose check";
+ LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::DEBUG);
+ }
+
+ LOG(VERBOSE) << "argparse: argc=" << argc;
+
+ for (int arg = 1; arg < argc; ++arg) {
+ std::string argstr = argv[arg];
+
+ LOG(VERBOSE) << "argparse: argv[" << arg << "]=" << argstr;
+ }
+
+ // Useful to attach a debugger...
+ // 1) $> iorap.cmd.readahead -w <args>
+ // 2) $> gdbclient <pid>
+ if (wait_for_keystroke) {
+ LOG(INFO) << "Self pid: " << getpid();
+
+ raise(SIGSTOP);
+ // LOG(INFO) << "Press any key to continue...";
+ // std::cin >> wait_for_keystroke;
+ }
+
+ // auto system_call = std::make_unique<SystemCallImpl>();
+ // TODO: mock readahead calls?
+ //
+ // Uncomment this if we want to leave the process around to inspect it from adb shell.
+ // sleep(100000);
+
+ int return_code = 0;
+
+ LOG(VERBOSE) << "Hello world";
+
+ ReadAhead read_ahead{arg_use_sockets}; // Don't count the time it takes to fork+exec.
+
+ size_t task_id_counter = 0;
+ for (const std::string& compiled_trace_path : arg_input_filenames) {
+ TaskId task_id{task_id_counter++, compiled_trace_path};
+
+ LOG(DEBUG) << "main: ReadAhead BeginTask: "
+ << "task_duration_ms=" << arg_task_duration_ms << ","
+ << task_id;
+
+ read_ahead.BeginTask(task_id);
+ usleep(arg_task_duration_ms*1000);
+
+ LOG(DEBUG) << "main: ReadAhead FinishTask: " << task_id;
+
+ read_ahead.FinishTask(task_id);
+ }
+ LOG(VERBOSE) << "main: Terminating";
+
+ // 0 -> successfully executed all commands.
+ // 1 -> failed along the way (#on_error and also see the error logs).
+ return return_code;
+}
+
+} // namespace iorap::prefetcher
+
+#if defined(IORAP_PREFETCHER_MAIN_CLIENT)
+int main(int argc, char** argv) {
+ return ::iorap::prefetcher::MainClient(argc, argv);
+}
+
+#endif // IORAP_PREFETCHER_MAIN_CLIENT
diff --git a/src/prefetcher/minijail.cc b/src/prefetcher/minijail.cc
new file mode 100644
index 0000000..df4b11b
--- /dev/null
+++ b/src/prefetcher/minijail.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 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 "prefetcher/minijail.h"
+
+#include <android-base/logging.h>
+#include <libminijail.h>
+
+namespace iorap::prefetcher {
+
+static const char kSeccompFilePath[] = "/system/etc/seccomp_policy/iorap.prefetcherd.policy";
+
+bool MiniJail() {
+ /* no seccomp policy for this architecture */
+ if (access(kSeccompFilePath, R_OK) == -1) {
+ LOG(WARNING) << "No seccomp filter defined for this architecture.";
+ return true;
+ }
+
+ struct minijail* jail = minijail_new();
+ if (jail == NULL) {
+ LOG(WARNING) << "Failed to create minijail.";
+ return false;
+ }
+
+ minijail_no_new_privs(jail);
+ minijail_log_seccomp_filter_failures(jail);
+ minijail_use_seccomp_filter(jail);
+ minijail_parse_seccomp_filters(jail, kSeccompFilePath);
+ minijail_enter(jail);
+ minijail_destroy(jail);
+
+ LOG(DEBUG) << "minijail installed.";
+
+ return true;
+}
+
+}
diff --git a/src/prefetcher/minijail.h b/src/prefetcher/minijail.h
new file mode 100644
index 0000000..9caefe6
--- /dev/null
+++ b/src/prefetcher/minijail.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 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_PREFETCHER_MINIJAIL_H
+#define SRC_PREFETCHER_MINIJAIL_H
+
+namespace iorap::prefetcher {
+// Install a minijail using seccomp_policy/prefetcherd.{ARCH}.policy file.
+bool MiniJail();
+}
+
+#endif // SRC_PREFETCHER_MINIJAIL_H
diff --git a/src/prefetcher/prefetcher_daemon.cc b/src/prefetcher/prefetcher_daemon.cc
new file mode 100644
index 0000000..f4b9087
--- /dev/null
+++ b/src/prefetcher/prefetcher_daemon.cc
@@ -0,0 +1,1367 @@
+// Copyright (C) 2019 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 "prefetcher/minijail.h"
+#include "common/cmd_utils.h"
+#include "prefetcher/prefetcher_daemon.h"
+#include "prefetcher/session_manager.h"
+#include "prefetcher/session.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+#include <deque>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+namespace iorap::prefetcher {
+
+// Gate super-spammy IPC logging behind a property.
+// This is beyond merely annoying, enabling this logging causes prefetching to be about 1000x slower.
+static bool LogVerboseIpc() {
+ static bool initialized = false;
+ static bool verbose_ipc;
+
+ if (initialized == false) {
+ initialized = true;
+
+ verbose_ipc =
+ ::android::base::GetBoolProperty("iorapd.readahead.verbose_ipc", /*default*/false);
+ }
+
+ return verbose_ipc;
+}
+
+static const bool kInstallMiniJail =
+ ::android::base::GetBoolProperty("iorapd.readahead.minijail", /*default*/true);
+
+static constexpr const char kCommandFileName[] = "/system/bin/iorap.prefetcherd";
+
+static constexpr size_t kPipeBufferSize = 1024 * 1024; // matches /proc/sys/fs/pipe-max-size
+
+using ArgString = const char*;
+
+std::ostream& operator<<(std::ostream& os, ReadAheadKind ps) {
+ switch (ps) {
+ case ReadAheadKind::kFadvise:
+ os << "fadvise";
+ break;
+ case ReadAheadKind::kMmapLocked:
+ os << "mmap";
+ break;
+ case ReadAheadKind::kMlock:
+ os << "mlock";
+ break;
+ default:
+ os << "<invalid>";
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, CommandChoice choice) {
+ switch (choice) {
+ case CommandChoice::kRegisterFilePath:
+ os << "kRegisterFilePath";
+ break;
+ case CommandChoice::kUnregisterFilePath:
+ os << "kUnregisterFilePath";
+ break;
+ case CommandChoice::kReadAhead:
+ os << "kReadAhead";
+ break;
+ case CommandChoice::kExit:
+ os << "kExit";
+ break;
+ case CommandChoice::kCreateSession:
+ os << "kCreateSession";
+ break;
+ case CommandChoice::kDestroySession:
+ os << "kDestroySession";
+ break;
+ case CommandChoice::kDumpSession:
+ os << "kDumpSession";
+ break;
+ case CommandChoice::kDumpEverything:
+ os << "kDumpEverything";
+ break;
+ case CommandChoice::kCreateFdSession:
+ os << "kCreateFdSession";
+ break;
+ default:
+ CHECK(false) << "forgot to handle this choice";
+ break;
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const Command& command) {
+ os << "Command{";
+ os << "choice=" << command.choice << ",";
+
+ bool has_session_id = true;
+ bool has_id = true;
+ switch (command.choice) {
+ case CommandChoice::kDumpEverything:
+ case CommandChoice::kExit:
+ has_session_id = false;
+ FALLTHROUGH_INTENDED;
+ case CommandChoice::kCreateFdSession:
+ case CommandChoice::kCreateSession:
+ case CommandChoice::kDestroySession:
+ case CommandChoice::kDumpSession:
+ has_id = false;
+ break;
+ default:
+ break;
+ }
+
+ if (has_session_id) {
+ os << "sid=" << command.session_id << ",";
+ }
+
+ if (has_id) {
+ os << "id=" << command.id << ",";
+ }
+
+ switch (command.choice) {
+ case CommandChoice::kRegisterFilePath:
+ os << "file_path=";
+
+ if (command.file_path) {
+ os << *(command.file_path);
+ } else {
+ os << "(nullopt)";
+ }
+ break;
+ case CommandChoice::kUnregisterFilePath:
+ break;
+ case CommandChoice::kReadAhead:
+ os << "read_ahead_kind=" << command.read_ahead_kind << ",";
+ os << "length=" << command.length << ",";
+ os << "offset=" << command.offset << ",";
+ break;
+ case CommandChoice::kExit:
+ break;
+ case CommandChoice::kCreateFdSession:
+ os << "fd=";
+ if (command.fd.has_value()) {
+ os << command.fd.value();
+ } else {
+ os << "(nullopt)";
+ }
+ os << ",";
+ FALLTHROUGH_INTENDED;
+ case CommandChoice::kCreateSession:
+ os << "description=";
+ if (command.file_path) {
+ os << "'" << *(command.file_path) << "'";
+ } else {
+ os << "(nullopt)";
+ }
+ break;
+ case CommandChoice::kDestroySession:
+ break;
+ case CommandChoice::kDumpSession:
+ break;
+ case CommandChoice::kDumpEverything:
+ break;
+ default:
+ CHECK(false) << "forgot to handle this choice";
+ break;
+ }
+
+ os << "}";
+
+ return os;
+}
+
+template <typename T>
+struct ParseResult {
+ T value;
+ char* next_token;
+ size_t stream_size;
+
+ ParseResult() : value{}, next_token{nullptr}, stream_size{} {
+ }
+
+ constexpr operator bool() const {
+ return next_token != nullptr;
+ }
+};
+
+// Very spammy: Keep it off by default. Set to true if changing this code.
+static constexpr bool kDebugParsingRead = false;
+
+#define DEBUG_PREAD if (kDebugParsingRead) LOG(VERBOSE) << "ParsingRead "
+
+
+
+// Parse a strong type T from a buffer stream.
+// If there's insufficient space left to parse the value, an empty ParseResult is returned.
+template <typename T>
+ParseResult<T> ParsingRead(char* stream, size_t stream_size) {
+ if (stream == nullptr) {
+ DEBUG_PREAD << "stream was null";
+ return {};
+ }
+
+ if constexpr (std::is_same_v<T, std::string>) {
+ ParseResult<uint32_t> length = ParsingRead<uint32_t>(stream, stream_size);
+
+ if (!length) {
+ DEBUG_PREAD << "could not find length";
+ // Not enough bytes left?
+ return {};
+ }
+
+ ParseResult<std::string> string_result;
+ string_result.value.reserve(length);
+
+ stream = length.next_token;
+ stream_size = length.stream_size;
+
+ for (size_t i = 0; i < length.value; ++i) {
+ ParseResult<char> char_result = ParsingRead<char>(stream, stream_size);
+
+ stream = char_result.next_token;
+ stream_size = char_result.stream_size;
+
+ if (!char_result) {
+ DEBUG_PREAD << "too few chars in stream, expected length: " << length.value;
+ // Not enough bytes left?
+ return {};
+ }
+
+ string_result.value += char_result.value;
+
+ DEBUG_PREAD << "string preliminary is : " << string_result.value;
+ }
+
+ DEBUG_PREAD << "parsed string to: " << string_result.value;
+ string_result.next_token = stream;
+ return string_result;
+ } else {
+ if (sizeof(T) > stream_size) {
+ return {};
+ }
+
+ ParseResult<T> result;
+ result.next_token = stream + sizeof(T);
+ result.stream_size = stream_size - sizeof(T);
+
+ memcpy(&result.value, stream, sizeof(T));
+
+ return result;
+ }
+}
+
+// Convenience overload to chain multiple ParsingRead together.
+template <typename T, typename U>
+ParseResult<T> ParsingRead(ParseResult<U> result) {
+ return ParsingRead<T>(result.next_token, result.stream_size);
+}
+
+class CommandParser {
+ public:
+ CommandParser(PrefetcherForkParameters params) {
+ params_ = params;
+ }
+
+ std::vector<Command> ParseSocketCommands(bool& eof) {
+ eof = false;
+
+ std::vector<Command> commands_vec;
+
+ std::vector<char> buf_vector;
+ buf_vector.resize(1024*1024); // 1MB.
+ char* buf = &buf_vector[0];
+
+ // Binary only parsing. The higher level code can parse text
+ // with ifstream if it really wants to.
+ char* stream = &buf[0];
+ size_t stream_size = buf_vector.size();
+
+ while (true) {
+ if (stream_size == 0) {
+ // TODO: reply with an overflow command.
+ LOG(WARNING) << "prefetcher_daemon command overflow, dropping all commands.";
+ stream = &buf[0];
+ stream_size = buf_vector.size();
+ memset(&buf[0], /*c*/0, buf_vector.size());
+ }
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon block recvmsg for commands (fd=" << params_.input_fd << ")";
+ }
+
+ ssize_t count;
+ struct msghdr hdr;
+ memset(&hdr, 0, sizeof(hdr));
+
+ {
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(int))];
+ } control_un;
+ memset(&control_un, 0, sizeof(control_un));
+
+ /* Set 'control_un' to describe ancillary data that we want to receive */
+ control_un.cmh.cmsg_len = CMSG_LEN(sizeof(int)); /* fd is sizeof(int) */
+ control_un.cmh.cmsg_level = SOL_SOCKET;
+ control_un.cmh.cmsg_type = SCM_CREDENTIALS;
+
+ // the regular message data will be read into stream
+ struct iovec iov;
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = stream;
+ iov.iov_len = stream_size;
+
+ /* Set hdr fields to describe 'control_un' */
+ hdr.msg_control = control_un.control;
+ hdr.msg_controllen = sizeof(control_un.control);
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+ hdr.msg_name = nullptr; /* no peer address */
+ hdr.msg_namelen = 0;
+
+ count = TEMP_FAILURE_RETRY(recvmsg(params_.input_fd, &hdr, /*flags*/0));
+ }
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon recvmsg " << count << " for stream size:" << stream_size;
+ }
+
+ if (count < 0) {
+ PLOG(ERROR) << "failed to recvmsg from input fd";
+ break;
+ // TODO: let the daemon be restarted by higher level code?
+ } else if (count == 0) {
+ LOG(WARNING) << "prefetcher_daemon input_fd end-of-file; terminating";
+ eof = true;
+ break;
+ // TODO: let the daemon be restarted by higher level code?
+ }
+
+ {
+ /* Extract fd from ancillary data if present */
+ struct cmsghdr* hp;
+ hp = CMSG_FIRSTHDR(&hdr);
+ if (hp &&
+ // FIXME: hp->cmsg_len returns an absurdly large value. is it overflowing?
+ // (hp->cmsg_len == CMSG_LEN(sizeof(int))) &&
+ (hp->cmsg_level == SOL_SOCKET) &&
+ (hp->cmsg_type == SCM_RIGHTS)) {
+
+ int passed_fd = *(int*) CMSG_DATA(hp);
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon received FD " << passed_fd;
+ }
+
+ // tack the FD into our dequeue.
+ // we assume the FDs are sent in-order same as the regular iov are sent in-order.
+ longbuf_fds_.insert(longbuf_fds_.end(), passed_fd);
+ } else if (hp != nullptr) {
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon::read got CMSG but it wasn't matching SCM_RIGHTS,"
+ << "cmsg_len=" << hp->cmsg_len << ","
+ << "cmsg_level=" << hp->cmsg_level << ","
+ << "cmsg_type=" << hp->cmsg_type;
+ }
+ }
+ }
+
+ longbuf_.insert(longbuf_.end(), stream, stream + count);
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon updated longbuf size: " << longbuf_.size();
+ }
+
+ // reconstruct a stream of [iov_Command chdr_fd?]* back into [Command]*
+ {
+ if (longbuf_.size() == 0) {
+ break;
+ }
+
+ std::vector<char> v(longbuf_.begin(),
+ longbuf_.end());
+
+ std::vector<int> v_fds{longbuf_fds_.begin(), longbuf_fds_.end()};
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon longbuf_ size: " << v.size();
+ if (WOULD_LOG(VERBOSE)) {
+ std::stringstream dump;
+ dump << std::hex << std::setfill('0');
+ for (size_t i = 0; i < v.size(); ++i) {
+ dump << std::setw(2) << static_cast<unsigned>(v[i]);
+ }
+
+ LOG(VERBOSE) << "PrefetcherDaemon longbuf_ dump: " << dump.str();
+ }
+ LOG(VERBOSE) << "PrefetcherDaemon longbuf_fds_ size: " << v_fds.size();
+ if (WOULD_LOG(VERBOSE)) {
+ std::stringstream dump;
+ for (size_t i = 0; i < v_fds.size(); ++i) {
+ dump << v_fds[i] << ", ";
+ }
+
+ LOG(VERBOSE) << "PrefetcherDaemon longbuf_fds_ dump: " << dump.str();
+ }
+
+ }
+
+ size_t v_fds_off = 0;
+ size_t consumed_fds_total = 0;
+
+ size_t v_off = 0;
+ size_t consumed_bytes = std::numeric_limits<size_t>::max();
+ size_t consumed_total = 0;
+
+ while (true) {
+ std::optional<Command> maybe_command;
+ maybe_command = Command::Read(&v[v_off], v.size() - v_off, &consumed_bytes);
+ consumed_total += consumed_bytes;
+ // Normal every time we get to the end of a buffer.
+ if (!maybe_command) {
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "failed to read command, v_off=" << v_off << ",v_size:" << v.size();
+ }
+ break;
+ }
+
+ if (maybe_command->RequiresFd()) {
+ if (v_fds_off < v_fds.size()) {
+ maybe_command->fd = v_fds[v_fds_off++];
+ consumed_fds_total++;
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "Append the FD to " << *maybe_command;
+ }
+ } else {
+ LOG(WARNING) << "Failed to acquire FD for " << *maybe_command;
+ }
+ }
+
+ // in the next pass ignore what we already consumed.
+ v_off += consumed_bytes;
+
+ // true as long we don't hit the 'break' above.
+ DCHECK_EQ(v_off, consumed_total);
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "success to read command, v_off=" << v_off << ",v_size:" << v.size()
+ << "," << *maybe_command;
+
+ // Pretty-print a single command for debugging/testing.
+ LOG(VERBOSE) << *maybe_command;
+ }
+
+ // add to the commands we parsed.
+ commands_vec.push_back(*maybe_command);
+ }
+
+ // erase however many were consumed
+ longbuf_.erase(longbuf_.begin(), longbuf_.begin() + consumed_total);
+
+ // erase however many FDs were consumed.
+ longbuf_fds_.erase(longbuf_fds_.begin(), longbuf_fds_.begin() + consumed_fds_total);
+ }
+ break;
+ }
+
+ return commands_vec;
+ }
+
+ std::vector<Command> ParseCommands(bool& eof) {
+ eof = false;
+
+ std::vector<Command> commands_vec;
+
+ std::vector<char> buf_vector;
+ buf_vector.resize(kPipeBufferSize);
+ char* buf = &buf_vector[0];
+
+ // Binary only parsing. The higher level code can parse text
+ // with ifstream if it really wants to.
+ char* stream = &buf[0];
+ size_t stream_size = buf_vector.size();
+
+ while (true) {
+ if (stream_size == 0) {
+ // TODO: reply with an overflow command.
+ LOG(WARNING) << "prefetcher_daemon command overflow, dropping all commands.";
+ stream = &buf[0];
+ stream_size = buf_vector.size();
+ memset(&buf[0], /*c*/0, buf_vector.size());
+ }
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon block read for commands (fd=" << params_.input_fd << ")";
+ }
+ ssize_t count = TEMP_FAILURE_RETRY(read(params_.input_fd, stream, stream_size));
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon::read " << count << " for stream size:" << stream_size;
+ }
+
+ if (count < 0) {
+ PLOG(ERROR) << "failed to read from input fd";
+ break;
+ // TODO: let the daemon be restarted by higher level code?
+ } else if (count == 0) {
+ LOG(WARNING) << "prefetcher_daemon input_fd end-of-file; terminating";
+ eof = true;
+ break;
+ // TODO: let the daemon be restarted by higher level code?
+ }
+
+ longbuf_.insert(longbuf_.end(), stream, stream + count);
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon updated longbuf size: " << longbuf_.size();
+ }
+
+ std::optional<Command> maybe_command;
+ {
+ if (longbuf_.size() == 0) {
+ break;
+ }
+
+ std::vector<char> v(longbuf_.begin(),
+ longbuf_.end());
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon longbuf_ size: " << v.size();
+ if (WOULD_LOG(VERBOSE)) {
+ std::stringstream dump;
+ dump << std::hex << std::setfill('0');
+ for (size_t i = 0; i < v.size(); ++i) {
+ dump << std::setw(2) << static_cast<unsigned>(v[i]);
+ }
+
+ LOG(VERBOSE) << "PrefetcherDaemon longbuf_ dump: " << dump.str();
+ }
+ }
+
+ size_t v_off = 0;
+ size_t consumed_bytes = std::numeric_limits<size_t>::max();
+ size_t consumed_total = 0;
+
+ while (true) {
+ maybe_command = Command::Read(&v[v_off], v.size() - v_off, &consumed_bytes);
+ consumed_total += consumed_bytes;
+ // Normal every time we get to the end of a buffer.
+ if (!maybe_command) {
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "failed to read command, v_off=" << v_off << ",v_size:" << v.size();
+ }
+ break;
+ }
+
+ // in the next pass ignore what we already consumed.
+ v_off += consumed_bytes;
+
+ // true as long we don't hit the 'break' above.
+ DCHECK_EQ(v_off, consumed_total);
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "success to read command, v_off=" << v_off << ",v_size:" << v.size()
+ << "," << *maybe_command;
+
+ // Pretty-print a single command for debugging/testing.
+ LOG(VERBOSE) << *maybe_command;
+ }
+
+ // add to the commands we parsed.
+ commands_vec.push_back(*maybe_command);
+ }
+
+ // erase however many were consumed
+ longbuf_.erase(longbuf_.begin(), longbuf_.begin() + consumed_total);
+ }
+ break;
+ }
+
+ return commands_vec;
+ }
+
+ private:
+ bool IsTextMode() const {
+ return params_.format_text;
+ }
+
+ PrefetcherForkParameters params_;
+
+ // A buffer long enough to contain a lot of buffers.
+ // This handles reads that only contain a partial command.
+ std::deque<char> longbuf_;
+
+ // File descriptor buffers.
+ std::deque<int> longbuf_fds_;
+};
+
+static constexpr bool kDebugCommandRead = true;
+
+#define DEBUG_READ if (kDebugCommandRead) LOG(VERBOSE) << "Command::Read "
+
+std::optional<Command> Command::Read(char* buf, size_t buf_size, /*out*/size_t* consumed_bytes) {
+ *consumed_bytes = 0;
+ if (buf == nullptr) {
+ return std::nullopt;
+ }
+
+ Command cmd{}; // zero-initialize any unused fields
+ ParseResult<CommandChoice> parsed_choice = ParsingRead<CommandChoice>(buf, buf_size);
+ cmd.choice = parsed_choice.value;
+
+ if (!parsed_choice) {
+ DEBUG_READ << "no choice";
+ return std::nullopt;
+ }
+
+ switch (parsed_choice.value) {
+ case CommandChoice::kRegisterFilePath: {
+ ParseResult<uint32_t> parsed_session_id = ParsingRead<uint32_t>(parsed_choice);
+ if (!parsed_session_id) {
+ DEBUG_READ << "no parsed session id";
+ return std::nullopt;
+ }
+
+ ParseResult<uint32_t> parsed_id = ParsingRead<uint32_t>(parsed_session_id);
+ if (!parsed_id) {
+ DEBUG_READ << "no parsed id";
+ return std::nullopt;
+ }
+
+ ParseResult<std::string> parsed_file_path = ParsingRead<std::string>(parsed_id);
+
+ if (!parsed_file_path) {
+ DEBUG_READ << "no file path";
+ return std::nullopt;
+ }
+ *consumed_bytes = parsed_file_path.next_token - buf;
+
+ cmd.session_id = parsed_session_id.value;
+ cmd.id = parsed_id.value;
+ cmd.file_path = parsed_file_path.value;
+
+ break;
+ }
+ case CommandChoice::kUnregisterFilePath: {
+ ParseResult<uint32_t> parsed_session_id = ParsingRead<uint32_t>(parsed_choice);
+ if (!parsed_session_id) {
+ DEBUG_READ << "no parsed session id";
+ return std::nullopt;
+ }
+
+ ParseResult<uint32_t> parsed_id = ParsingRead<uint32_t>(parsed_session_id);
+ if (!parsed_id) {
+ DEBUG_READ << "no parsed id";
+ return std::nullopt;
+ }
+ *consumed_bytes = parsed_id.next_token - buf;
+
+ cmd.session_id = parsed_session_id.value;
+ cmd.id = parsed_id.value;
+
+ break;
+ }
+ case CommandChoice::kReadAhead: {
+ ParseResult<uint32_t> parsed_session_id = ParsingRead<uint32_t>(parsed_choice);
+ if (!parsed_session_id) {
+ DEBUG_READ << "no parsed session id";
+ return std::nullopt;
+ }
+
+ ParseResult<uint32_t> parsed_id = ParsingRead<uint32_t>(parsed_session_id);
+ if (!parsed_id) {
+ DEBUG_READ << "no parsed id";
+ return std::nullopt;
+ }
+
+ ParseResult<ReadAheadKind> parsed_kind = ParsingRead<ReadAheadKind>(parsed_id);
+ if (!parsed_kind) {
+ DEBUG_READ << "no parsed kind";
+ return std::nullopt;
+ }
+ ParseResult<uint64_t> parsed_length = ParsingRead<uint64_t>(parsed_kind);
+ if (!parsed_length) {
+ DEBUG_READ << "no parsed length";
+ return std::nullopt;
+ }
+ ParseResult<uint64_t> parsed_offset = ParsingRead<uint64_t>(parsed_length);
+ if (!parsed_offset) {
+ DEBUG_READ << "no parsed offset";
+ return std::nullopt;
+ }
+ *consumed_bytes = parsed_offset.next_token - buf;
+
+ cmd.session_id = parsed_session_id.value;
+ cmd.id = parsed_id.value;
+ cmd.read_ahead_kind = parsed_kind.value;
+ cmd.length = parsed_length.value;
+ cmd.offset = parsed_offset.value;
+
+ break;
+ }
+ case CommandChoice::kCreateSession:
+ case CommandChoice::kCreateFdSession: {
+ ParseResult<uint32_t> parsed_session_id = ParsingRead<uint32_t>(parsed_choice);
+ if (!parsed_session_id) {
+ DEBUG_READ << "no parsed session id";
+ return std::nullopt;
+ }
+
+ ParseResult<std::string> parsed_description = ParsingRead<std::string>(parsed_session_id);
+
+ if (!parsed_description) {
+ DEBUG_READ << "no description";
+ return std::nullopt;
+ }
+ *consumed_bytes = parsed_description.next_token - buf;
+
+ cmd.session_id = parsed_session_id.value;
+ cmd.file_path = parsed_description.value;
+
+ break;
+ }
+ case CommandChoice::kDestroySession:
+ case CommandChoice::kDumpSession: {
+ ParseResult<uint32_t> parsed_session_id = ParsingRead<uint32_t>(parsed_choice);
+ if (!parsed_session_id) {
+ DEBUG_READ << "no parsed session id";
+ return std::nullopt;
+ }
+
+ *consumed_bytes = parsed_session_id.next_token - buf;
+
+ cmd.session_id = parsed_session_id.value;
+
+ break;
+ }
+ case CommandChoice::kExit:
+ case CommandChoice::kDumpEverything:
+ *consumed_bytes = parsed_choice.next_token - buf;
+ // Only need to parse the choice.
+ break;
+ default:
+ LOG(FATAL) << "unrecognized command number " << static_cast<uint32_t>(parsed_choice.value);
+ break;
+ }
+
+ return cmd;
+}
+
+bool Command::Write(char* buf, size_t buf_size, /*out*/size_t* produced_bytes) const {
+ *produced_bytes = 0;
+ if (buf == nullptr) {
+ LOG(WARNING) << "null buf, is this expected?";
+ return false;
+ }
+
+ bool has_enough_space = false;
+ size_t space_requirement = std::numeric_limits<size_t>::max();
+
+ space_requirement = sizeof(choice);
+
+ switch (choice) {
+ case CommandChoice::kRegisterFilePath:
+ space_requirement += sizeof(session_id);
+ space_requirement += sizeof(id);
+ space_requirement += sizeof(uint32_t); // string length
+
+ if (!file_path) {
+ LOG(WARNING) << "Missing file path for kRegisterFilePath";
+ return false;
+ }
+
+ space_requirement += file_path->size(); // string contents
+ break;
+ case CommandChoice::kUnregisterFilePath:
+ space_requirement += sizeof(session_id);
+ space_requirement += sizeof(id);
+ break;
+ case CommandChoice::kReadAhead:
+ space_requirement += sizeof(session_id);
+ space_requirement += sizeof(id);
+ space_requirement += sizeof(read_ahead_kind);
+ space_requirement += sizeof(length);
+ space_requirement += sizeof(offset);
+ break;
+ case CommandChoice::kCreateSession:
+ case CommandChoice::kCreateFdSession:
+ space_requirement += sizeof(session_id);
+ space_requirement += sizeof(uint32_t); // string length
+
+ if (!file_path) {
+ LOG(WARNING) << "Missing file path for kCreateSession";
+ return false;
+ }
+
+ space_requirement += file_path->size(); // string contents
+ break;
+ case CommandChoice::kDestroySession:
+ case CommandChoice::kDumpSession:
+ space_requirement += sizeof(session_id);
+ break;
+ case CommandChoice::kExit:
+ case CommandChoice::kDumpEverything:
+ // Only need space for the choice.
+ break;
+ default:
+ LOG(FATAL) << "unrecognized command number " << static_cast<uint32_t>(choice);
+ break;
+ }
+
+ if (buf_size < space_requirement) {
+ return false;
+ }
+
+ *produced_bytes = space_requirement;
+
+ // Always write out the choice.
+ size_t buf_offset = 0;
+
+ memcpy(&buf[buf_offset], &choice, sizeof(choice));
+ buf_offset += sizeof(choice);
+
+ switch (choice) {
+ case CommandChoice::kRegisterFilePath:
+ memcpy(&buf[buf_offset], &session_id, sizeof(session_id));
+ buf_offset += sizeof(session_id);
+ memcpy(&buf[buf_offset], &id, sizeof(id));
+ buf_offset += sizeof(id);
+
+ {
+ uint32_t string_length = static_cast<uint32_t>(file_path->size());
+ memcpy(&buf[buf_offset], &string_length, sizeof(string_length));
+ buf_offset += sizeof(string_length);
+ }
+
+ DCHECK(file_path.has_value());
+
+ memcpy(&buf[buf_offset], file_path->c_str(), file_path->size());
+ buf_offset += file_path->size();
+ break;
+ case CommandChoice::kUnregisterFilePath:
+ memcpy(&buf[buf_offset], &session_id, sizeof(session_id));
+ buf_offset += sizeof(session_id);
+ memcpy(&buf[buf_offset], &id, sizeof(id));
+ buf_offset += sizeof(id);
+ break;
+ case CommandChoice::kReadAhead:
+ memcpy(&buf[buf_offset], &session_id, sizeof(session_id));
+ buf_offset += sizeof(session_id);
+ memcpy(&buf[buf_offset], &id, sizeof(id));
+ buf_offset += sizeof(id);
+ memcpy(&buf[buf_offset], &read_ahead_kind, sizeof(read_ahead_kind));
+ buf_offset += sizeof(read_ahead_kind);
+ memcpy(&buf[buf_offset], &length, sizeof(length));
+ buf_offset += sizeof(length);
+ memcpy(&buf[buf_offset], &offset, sizeof(offset));
+ buf_offset += sizeof(offset);
+ break;
+ case CommandChoice::kCreateSession:
+ case CommandChoice::kCreateFdSession:
+ memcpy(&buf[buf_offset], &session_id, sizeof(session_id));
+ buf_offset += sizeof(session_id);
+
+ {
+ uint32_t string_length = static_cast<uint32_t>(file_path->size());
+ memcpy(&buf[buf_offset], &string_length, sizeof(string_length));
+ buf_offset += sizeof(string_length);
+ }
+
+ DCHECK(file_path.has_value());
+
+ memcpy(&buf[buf_offset], file_path->c_str(), file_path->size());
+ buf_offset += file_path->size();
+
+ DCHECK_EQ(buf_offset, space_requirement) << *this << ",file_path_size:" << file_path->size();
+ DCHECK_EQ(buf_offset, *produced_bytes) << *this;
+
+ break;
+ case CommandChoice::kDestroySession:
+ case CommandChoice::kDumpSession:
+ memcpy(&buf[buf_offset], &session_id, sizeof(session_id));
+ buf_offset += sizeof(session_id);
+ break;
+ case CommandChoice::kExit:
+ case CommandChoice::kDumpEverything:
+ // Only need to write out the choice.
+ break;
+ default:
+ LOG(FATAL) << "should have fallen out in the above switch"
+ << static_cast<uint32_t>(choice);
+ break;
+ }
+
+ DCHECK_EQ(buf_offset, space_requirement) << *this;
+ DCHECK_EQ(buf_offset, *produced_bytes) << *this;
+
+ return true;
+}
+
+class PrefetcherDaemon::Impl {
+ public:
+ std::optional<PrefetcherForkParameters> StartPipesViaFork() {
+ int pipefds[2];
+ if (pipe(&pipefds[0]) != 0) {
+ PLOG(FATAL) << "Failed to create read/write pipes";
+ }
+
+ if (WOULD_LOG(VERBOSE)) {
+ long pipe_size = static_cast<long>(fcntl(pipefds[0], F_GETPIPE_SZ));
+ if (pipe_size < 0) {
+ PLOG(ERROR) << "Failed to F_GETPIPE_SZ:";
+ }
+ LOG(VERBOSE) << "StartPipesViaFork: default pipe size: " << pipe_size;
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ // Default pipe size is usually 64KB.
+ // Increase to 1MB so that iorapd has to rarely run during prefetching.
+ if (fcntl(pipefds[i], F_SETPIPE_SZ, kPipeBufferSize) < 0) {
+ PLOG(FATAL) << "Failed to increase pipe size to max";
+ }
+ }
+
+ pipefd_read_ = pipefds[0];
+ pipefd_write_ = pipefds[1];
+
+ PrefetcherForkParameters params;
+ params.input_fd = pipefd_read_;
+ params.output_fd = pipefd_write_;
+ params.format_text = false;
+ params.use_sockets = false;
+
+ bool res = StartViaFork(params);
+ if (res) {
+ return params;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+std::optional<PrefetcherForkParameters> StartSocketViaFork() {
+ int socket_fds[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, /*protocol*/0, &socket_fds[0]) != 0) {
+ PLOG(FATAL) << "Failed to create read/write socketpair";
+ }
+
+ pipefd_read_ = socket_fds[0]; // iorapd writer, iorap.prefetcherd reader
+ pipefd_write_ = socket_fds[1]; // iorapd reader, iorap.prefetcherd writer
+
+ PrefetcherForkParameters params;
+ params.input_fd = pipefd_read_;
+ params.output_fd = pipefd_write_;
+ params.format_text = false;
+ params.use_sockets = true;
+
+ bool res = StartViaFork(params);
+ if (res) {
+ return params;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ bool StartViaFork(PrefetcherForkParameters params) {
+ params_ = params;
+
+ forked_ = true;
+ child_ = fork();
+
+ if (child_ == -1) {
+ LOG(FATAL) << "Failed to fork PrefetcherDaemon";
+ } else if (child_ > 0) { // we are the caller of this function
+ LOG(DEBUG) << "forked into iorap.prefetcherd, pid = " << child_;
+
+ return true;
+ } else {
+ // we are the child that was forked.
+ std::stringstream argv; // for logging
+ std::vector<std::string> argv_vec;
+
+ {
+ std::stringstream s;
+ s << "--input-fd";
+ argv_vec.push_back(s.str());
+
+ std::stringstream s2;
+ s2 << params.input_fd;
+ argv_vec.push_back(s2.str());
+
+ argv << " --input-fd" << " " << params.input_fd;
+ }
+
+ {
+ std::stringstream s;
+ s << "--output-fd";
+ argv_vec.push_back(s.str());
+
+ std::stringstream s2;
+ s2 << params.output_fd;
+ argv_vec.push_back(s2.str());
+
+ argv << " --output-fd" << " " << params.output_fd;
+ }
+
+
+ if (params.use_sockets) {
+ std::stringstream s;
+ s << "--use-sockets";
+ argv_vec.push_back(s.str());
+
+ argv << " --use-sockets";
+ }
+
+ if (WOULD_LOG(VERBOSE)) {
+ std::stringstream s;
+ s << "--verbose";
+ argv_vec.push_back(s.str());
+
+ argv << " --verbose";
+ }
+
+ std::unique_ptr<ArgString[]> argv_ptr = common::VecToArgv(kCommandFileName, argv_vec);
+
+ LOG(DEBUG) << "fork+exec: " << kCommandFileName << " "
+ << argv.str();
+ execve(kCommandFileName, (char **)argv_ptr.get(), /*envp*/nullptr);
+ // This should never return.
+ _exit(EXIT_FAILURE);
+ }
+
+ DCHECK(false);
+ return false;
+ }
+
+ // TODO: Not very useful since this can never return 'true'
+ // -> in the child we would've already execd which loses all this code.
+ bool IsDaemon() {
+ // In the child the pid is always 0.
+ return child_ > 0;
+ }
+
+ bool Main(PrefetcherForkParameters params) {
+ LOG(VERBOSE) << "PrefetcherDaemon::Main " << params;
+
+ CommandParser command_parser{params};
+
+ Command next_command{};
+
+ std::vector<Command> many_commands;
+
+ // Ensure alogd is pre-initialized before installing minijail.
+ LOG(DEBUG) << "Installing minijail";
+
+ // Install seccomp filter using libminijail.
+ if (kInstallMiniJail) {
+ MiniJail();
+ }
+
+ while (true) {
+ bool eof = false;
+
+ if (params.use_sockets) {
+ // use recvmsg(2). supports receiving FDs.
+ many_commands = command_parser.ParseSocketCommands(/*out*/eof);
+ } else {
+ // use read(2). does not support receiving FDs.
+ many_commands = command_parser.ParseCommands(/*out*/eof);
+ }
+
+ if (eof) {
+ LOG(WARNING) << "PrefetcherDaemon got EOF, terminating";
+ return true;
+ }
+
+ for (auto& command : many_commands) {
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "PrefetcherDaemon got command: " << command;
+ }
+
+ if (command.choice == CommandChoice::kExit) {
+ LOG(DEBUG) << "PrefetcherDaemon got kExit command, terminating";
+ return true;
+ }
+
+ if (!ReceiveCommand(command)) {
+ // LOG(WARNING) << "PrefetcherDaemon command processing failure: " << command;
+ }
+
+ // ReceiveCommand should dup to keep the FD. Avoid leaks.
+ if (command.fd.has_value()) {
+ close(*command.fd);
+ }
+ }
+ }
+
+ LOG(VERBOSE) << "PrefetcherDaemon::Main got exit, terminating";
+
+ return true;
+ // Terminate.
+ }
+
+ Impl(PrefetcherDaemon* daemon) {
+ session_manager_ = SessionManager::CreateManager(SessionKind::kInProcessDirect);
+ DCHECK(session_manager_ != nullptr);
+ };
+
+ ~Impl() {
+ // Don't do anything if we never called 'StartViaFork'
+ if (forked_) {
+ if (!IsDaemon()) {
+ int status;
+ waitpid(child_, /*out*/&status, /*options*/0);
+ } else {
+ LOG(WARNING) << "execve should have avoided this path";
+ // DCHECK(false) << "not possible because the execve would avoid this path";
+ }
+ }
+ }
+
+ bool SendCommand(const Command& command) {
+ // Only parent is the sender.
+ DCHECK(forked_);
+ //DCHECK(!IsDaemon());
+
+ char buf[1024];
+ size_t stream_size;
+ if (!command.Write(buf, sizeof(buf), /*out*/&stream_size)) {
+ PLOG(ERROR) << "Failed to serialize command: " << command;
+ return false;
+ }
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "pre-write(fd=" << pipefd_write_ << ", buf=" << buf
+ << ", size=" << stream_size<< ")";
+ }
+
+ if (params_.use_sockets) {
+ /* iov contains the normal message (Command) */
+ struct iovec iov;
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = &buf[0];
+ iov.iov_len = stream_size;
+
+ struct msghdr msg;
+ memset(&msg, 0, sizeof(msg));
+
+ /* point to iov to transmit */
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ /* no dest address; socket is connected */
+ msg.msg_name = nullptr;
+ msg.msg_namelen = 0;
+
+ // append a CMSG with SCM_RIGHTS if we have an FD.
+ if (command.fd.has_value()) {
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(int))]; /* sized to hold an fd (int) */
+ } control_un;
+ memset(&control_un, 0, sizeof(control_un));
+
+ msg.msg_control = &control_un.control[0];
+ msg.msg_controllen = sizeof(control_un.control);
+
+ struct cmsghdr *hp;
+ hp = CMSG_FIRSTHDR(&msg);
+ hp->cmsg_len = CMSG_LEN(sizeof(int));
+ hp->cmsg_level = SOL_SOCKET;
+ hp->cmsg_type = SCM_RIGHTS;
+ *((int *) CMSG_DATA(hp)) = *(command.fd);
+
+ DCHECK(command.RequiresFd()) << command;
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "append FD to sendmsg: " << *(command.fd);
+ }
+ }
+
+ // TODO: add CMSG for the FD passage.
+
+ if (TEMP_FAILURE_RETRY(sendmsg(pipefd_write_, &msg, /*flags*/0)) < 0) {
+ PLOG(ERROR) << "Failed to sendmsg command: " << command;
+ return false;
+ }
+ } else {
+ if (TEMP_FAILURE_RETRY(write(pipefd_write_, buf, stream_size)) < 0) {
+ PLOG(ERROR) << "Failed to write command: " << command;
+ return false;
+ }
+ }
+
+ if (LogVerboseIpc()) {
+ LOG(VERBOSE) << "write(fd=" << pipefd_write_ << ", buf=" << buf
+ << ", size=" << stream_size<< ")";
+ }
+
+ // TODO: also read the reply?
+ return true;
+ }
+
+ bool ReceiveCommand(const Command& command) {
+ // Only child is the command receiver.
+ // DCHECK(IsDaemon());
+
+ switch (command.choice) {
+ case CommandChoice::kRegisterFilePath: {
+ std::shared_ptr<Session> session = session_manager_->FindSession(command.session_id);
+
+ if (!session) {
+ LOG(ERROR) << "ReceiveCommand: Could not find session for command: " << command;
+ return false;
+ }
+
+ CHECK(command.file_path.has_value()) << command;
+ return session->RegisterFilePath(command.id, *command.file_path);
+ }
+ case CommandChoice::kUnregisterFilePath: {
+ std::shared_ptr<Session> session = session_manager_->FindSession(command.session_id);
+
+ if (!session) {
+ LOG(ERROR) << "ReceiveCommand: Could not find session for command: " << command;
+ return false;
+ }
+
+ return session->UnregisterFilePath(command.id);
+ }
+ case CommandChoice::kReadAhead: {
+ std::shared_ptr<Session> session = session_manager_->FindSession(command.session_id);
+
+ if (!session) {
+ LOG(ERROR) << "ReceiveCommand: Could not find session for command: " << command;
+ return false;
+ }
+
+ return session->ReadAhead(command.id, command.read_ahead_kind, command.length, command.offset);
+ }
+ // TODO: unreadahead
+ case CommandChoice::kExit: {
+ LOG(WARNING) << "kExit should be handled earlier.";
+ return true;
+ }
+ case CommandChoice::kCreateSession: {
+ std::shared_ptr<Session> session = session_manager_->FindSession(command.session_id);
+ if (session != nullptr) {
+ LOG(ERROR) << "ReceiveCommand: session for ID already exists: " << command;
+ return false;
+ }
+ CHECK(command.file_path.has_value()) << command;
+ if (session_manager_->CreateSession(command.session_id, /*description*/*command.file_path)
+ == nullptr) {
+ LOG(ERROR) << "ReceiveCommand: Failure to kCreateSession: " << command;
+ return false;
+ }
+ return true;
+ }
+ case CommandChoice::kDestroySession: {
+ if (!session_manager_->DestroySession(command.session_id)) {
+ LOG(ERROR) << "ReceiveCommand: Failure to kDestroySession: " << command;
+ return false;
+ }
+ return true;
+ }
+ case CommandChoice::kDumpSession: {
+ std::shared_ptr<Session> session = session_manager_->FindSession(command.session_id);
+
+ if (!session) {
+ LOG(ERROR) << "ReceiveCommand: Could not find session for command: " << command;
+ return false;
+ }
+
+ // TODO: Consider doing dumpsys support somehow?
+ session->Dump(LOG_STREAM(DEBUG), /*multiline*/true);
+ return true;
+ }
+ case CommandChoice::kDumpEverything: {
+ session_manager_->Dump(LOG_STREAM(DEBUG), /*multiline*/true);
+ break;
+ }
+ case CommandChoice::kCreateFdSession: {
+ std::shared_ptr<Session> session = session_manager_->FindSession(command.session_id);
+ if (session != nullptr) {
+ LOG(ERROR) << "ReceiveCommand: session for ID already exists: " << command;
+ return false;
+ }
+ CHECK(command.file_path.has_value()) << command;
+ CHECK(command.fd.has_value()) << command;
+
+ LOG(VERBOSE) << "ReceiveCommand: kCreateFdSession fd=" << *(command.fd);
+
+ // TODO: Maybe use CreateFdSession instead?
+ session =
+ session_manager_->CreateSession(command.session_id,
+ /*description*/*command.file_path,
+ command.fd.value());
+ if (session == nullptr) {
+ LOG(ERROR) << "ReceiveCommand: Failure to kCreateFdSession: " << command;
+ return false;
+ }
+
+ return session->ProcessFd(*command.fd);
+ }
+ }
+
+ return true;
+ }
+
+ pid_t child_;
+ bool forked_;
+ int pipefd_read_;
+ int pipefd_write_;
+ PrefetcherForkParameters params_;
+ // do not ever use an indirect session manager here, as it would cause a lifetime cycle.
+ std::unique_ptr<SessionManager> session_manager_; // direct only.
+};
+
+PrefetcherDaemon::PrefetcherDaemon()
+ : impl_{new Impl{this}} {
+ LOG(VERBOSE) << "PrefetcherDaemon() constructor";
+}
+
+bool PrefetcherDaemon::StartViaFork(PrefetcherForkParameters params) {
+ return impl_->StartViaFork(std::move(params));
+}
+
+
+std::optional<PrefetcherForkParameters> PrefetcherDaemon::StartPipesViaFork() {
+ return impl_->StartPipesViaFork();
+}
+
+std::optional<PrefetcherForkParameters> PrefetcherDaemon::StartSocketViaFork() {
+ return impl_->StartSocketViaFork();
+}
+
+bool PrefetcherDaemon::Main(PrefetcherForkParameters params) {
+ return impl_->Main(params);
+}
+
+bool PrefetcherDaemon::SendCommand(const Command& command) {
+ return impl_->SendCommand(command);
+}
+
+PrefetcherDaemon::~PrefetcherDaemon() {
+ // required for unique_ptr for incomplete types.
+}
+
+} // namespace iorap::prefetcher
diff --git a/src/prefetcher/prefetcher_daemon.h b/src/prefetcher/prefetcher_daemon.h
new file mode 100644
index 0000000..693f871
--- /dev/null
+++ b/src/prefetcher/prefetcher_daemon.h
@@ -0,0 +1,130 @@
+// Copyright (C) 2019 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 PREFETCHER_DAEMON_H_
+#define PREFETCHER_DAEMON_H_
+
+#include "prefetcher/session_manager.h"
+
+#include <memory>
+#include <optional>
+#include <ostream>
+
+namespace iorap {
+namespace prefetcher {
+
+struct PrefetcherForkParameters {
+ int input_fd;
+ int output_fd;
+ bool use_sockets; // use the socket path instead of simpler read/write path.
+ bool format_text; // true=>text, false=>binary
+};
+
+inline std::ostream& operator<<(std::ostream& os, const PrefetcherForkParameters& p) {
+ os << "PrefetcherForkParameters{";
+ os << "input_fd=" << p.input_fd << ",";
+ os << "output_fd=" << p.output_fd << ",";
+ os << "format_text=" << p.format_text << ",";
+ os << "use_sockets=" << p.use_sockets << ",";
+ os << "}";
+ return os;
+}
+
+
+#ifndef READ_AHEAD_KIND
+enum class ReadAheadKind : uint32_t {
+ kFadvise = 0,
+ kMmapLocked = 1,
+ kMlock = 2,
+};
+#define READ_AHEAD_KIND 1
+#endif
+
+std::ostream& operator<<(std::ostream& os, ReadAheadKind k);
+
+enum class CommandChoice : uint32_t {
+ kRegisterFilePath, // kRegisterFilePath <sid:uint32> <id:uint32> <path:c-string>
+ kUnregisterFilePath, // kUnregisterFilePath <sid:uint32> <id:uint32>
+ kReadAhead, // kReadAhead <sid:uint32> <id:uint32> <kind:uint32_t> <length:uint64> <offset:uint64>
+ kExit, // kExit
+ kCreateSession, // kCreateSession <sid:uint32> <description:c-string>
+ kDestroySession, // kDestroySession <sid:uint32>
+ kDumpSession, // kDumpSession <sid:uint32>
+ kDumpEverything, // kDumpEverything
+ kCreateFdSession, // kCreateFdSession $CMSG{<fd:int>} <sid:uint32> <description:c-string>
+};
+
+struct Command {
+ CommandChoice choice;
+ uint32_t session_id;
+ uint32_t id; // file_path_id
+ std::optional<std::string> file_path; // required for choice=kRegisterFilePath.
+ // also serves as the description for choice=kCreateSession
+
+ // choice=kReadAhead
+ ReadAheadKind read_ahead_kind;
+ uint64_t length;
+ uint64_t offset;
+
+ std::optional<int> fd; // only valid in kCreateFdSession.
+
+ // Deserialize from a char buffer.
+ // This can only fail if buf_size is too small.
+ static std::optional<Command> Read(char* buf, size_t buf_size, /*out*/size_t* consumed_bytes);
+ // Serialize to a char buffer.
+ // This can only fail if the buf_size is too small.
+ bool Write(char* buf, size_t buf_size, /*out*/size_t* produced_bytes) const;
+
+ bool RequiresFd() const {
+ return choice == CommandChoice::kCreateFdSession;
+ }
+};
+
+std::ostream& operator<<(std::ostream& os, const Command& command);
+
+class PrefetcherDaemon {
+ public:
+ PrefetcherDaemon();
+ ~PrefetcherDaemon();
+
+ // Asynchronously launch a new fork.
+ //
+ // The destructor will waitpid automatically on the child process.
+ bool StartViaFork(PrefetcherForkParameters params);
+
+ // Launch a new fork , returning the pipes as input/output fds.
+ std::optional<PrefetcherForkParameters> StartPipesViaFork();
+
+ // Launch a new fork , returning the socket pair as input/output fds.
+ std::optional<PrefetcherForkParameters> StartSocketViaFork();
+
+ // Execute the main code in-process.
+ //
+ // Intended as the execve target.
+ bool Main(PrefetcherForkParameters params);
+
+ // Send a command via IPC.
+ // The caller must be the parent process after using StartViaFork.
+ bool SendCommand(const Command& command);
+
+ private:
+ class Impl;
+ std::unique_ptr<PrefetcherDaemon::Impl> impl_;
+};
+
+} // namespace prefetcher
+} // namespace iorap
+
+#endif
+
diff --git a/src/prefetcher/read_ahead.cc b/src/prefetcher/read_ahead.cc
new file mode 100644
index 0000000..164f2ff
--- /dev/null
+++ b/src/prefetcher/read_ahead.cc
@@ -0,0 +1,435 @@
+// Copyright (C) 2017 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 "read_ahead.h"
+
+#include "common/trace.h"
+#include "prefetcher/session_manager.h"
+#include "prefetcher/session.h"
+#include "prefetcher/task_id.h"
+#include "serialize/arena_ptr.h"
+#include "serialize/protobuf_io.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/scopeguard.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <cutils/trace.h>
+#include <deque>
+#include <fcntl.h>
+#include <functional>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unordered_map>
+#include <utils/Printer.h>
+
+namespace iorap {
+namespace prefetcher {
+
+enum class PrefetchStrategy {
+ kFadvise = 0,
+ kMmapLocked = 1,
+ kMlock = 2,
+};
+
+std::ostream& operator<<(std::ostream& os, PrefetchStrategy ps) {
+ switch (ps) {
+ case PrefetchStrategy::kFadvise:
+ os << "fadvise";
+ break;
+ case PrefetchStrategy::kMmapLocked:
+ os << "mmap";
+ break;
+ case PrefetchStrategy::kMlock:
+ os << "mlock";
+ break;
+ default:
+ os << "<invalid>";
+ }
+ return os;
+}
+
+static constexpr PrefetchStrategy kPrefetchStrategy = PrefetchStrategy::kFadvise;
+
+static PrefetchStrategy GetPrefetchStrategy() {
+ PrefetchStrategy strat = PrefetchStrategy::kFadvise;
+
+ std::string prefetch_env =
+ ::android::base::GetProperty("iorapd.readahead.strategy", /*default*/"");
+
+ if (prefetch_env == "") {
+ LOG(VERBOSE)
+ << "ReadAhead strategy defaulted. Did you want to set iorapd.readahead.strategy ?";
+ } else if (prefetch_env == "mmap") {
+ strat = PrefetchStrategy::kMmapLocked;
+ LOG(VERBOSE) << "ReadAhead strategy: kMmapLocked";
+ } else if (prefetch_env == "mlock") {
+ strat = PrefetchStrategy::kMlock;
+ LOG(VERBOSE) << "ReadAhead strategy: kMlock";
+ } else if (prefetch_env == "fadvise") {
+ strat = PrefetchStrategy::kFadvise;
+ LOG(VERBOSE) << "ReadAhead strategy: kFadvise";
+ } else {
+ LOG(WARNING) << "Unknown iorapd.readahead.strategy: " << prefetch_env << ", ignoring";
+ }
+
+ return strat;
+}
+
+struct TaskData {
+ TaskId task_id; // also the session ID.
+
+ size_t SessionId() const {
+ if (session != nullptr) {
+ DCHECK_EQ(session->SessionId(), task_id.id);
+ }
+
+ // good enough to be used as the session ID. Task IDs are always monotonically increasing.
+ return task_id.id;
+ }
+
+ std::shared_ptr<Session> session;
+ int32_t trace_cookie; // async trace cookie in BeginTask/FinishTask.
+};
+
+// Remember the last 5 files being prefetched.
+static constexpr size_t kRecentDataCount = 5;
+
+struct RecentData {
+ TaskId task_id;
+ size_t file_lengths_sum;
+};
+
+struct RecentDataKeeper {
+ std::deque<RecentData> recents_;
+ std::mutex mutex_;
+
+ void RecordRecent(TaskId task_id, size_t file_lengths_sum) {
+ std::lock_guard<std::mutex> guard{mutex_};
+
+ while (recents_.size() > kRecentDataCount) {
+ recents_.pop_front();
+ }
+ recents_.push_back(RecentData{std::move(task_id), file_lengths_sum});
+ }
+
+ void Dump(/*borrow*/::android::Printer& printer) {
+ bool locked = mutex_.try_lock();
+
+ printer.printFormatLine("Recent prefetches:");
+ if (!locked) {
+ printer.printLine(""""" (possible deadlock)");
+ }
+
+ for (const RecentData& data : recents_) {
+ printer.printFormatLine(" %s", data.task_id.path.c_str());
+ printer.printFormatLine(" Task ID: %zu", data.task_id.id);
+ printer.printFormatLine(" Bytes count: %zu", data.file_lengths_sum);
+ }
+
+ if (recents_.empty()) {
+ printer.printFormatLine(" (None)");
+ }
+
+ printer.printLine("");
+
+ if (locked) {
+ mutex_.unlock();
+ }
+ }
+};
+
+struct ReadAhead::Impl {
+ Impl(bool use_sockets) {
+ // Flip this property to test in-process vs out-of-process for the prefetcher code.
+ bool out_of_process =
+ ::android::base::GetBoolProperty("iorapd.readahead.out_of_process", /*default*/true);
+
+ SessionKind session_kind =
+ out_of_process ? SessionKind::kOutOfProcessIpc : SessionKind::kInProcessDirect;
+
+ if (use_sockets) {
+ session_kind = SessionKind::kOutOfProcessSocket;
+ }
+
+ session_manager_ = SessionManager::CreateManager(session_kind);
+ session_kind_ = session_kind;
+ }
+
+ std::unique_ptr<SessionManager> session_manager_;
+ SessionKind session_kind_;
+ std::unordered_map<size_t /*task index*/, TaskData> read_ahead_file_map_;
+ static RecentDataKeeper recent_data_keeper_;
+ int32_t trace_cookie_{0};
+
+ bool UseSockets() const {
+ return session_kind_ == SessionKind::kOutOfProcessSocket;
+ }
+};
+
+RecentDataKeeper ReadAhead::Impl::recent_data_keeper_{};
+
+ReadAhead::ReadAhead() : ReadAhead(/*use_sockets*/false) {
+}
+
+ReadAhead::ReadAhead(bool use_sockets) : impl_(new Impl(use_sockets)) {
+}
+
+ReadAhead::~ReadAhead() {}
+
+static bool PerformReadAhead(std::shared_ptr<Session> session, size_t path_id, ReadAheadKind kind, size_t length, size_t offset) {
+ return session->ReadAhead(path_id, kind, length, offset);
+}
+
+void ReadAhead::FinishTask(const TaskId& id) {
+ auto it = impl_->read_ahead_file_map_.find(id.id);
+ if (it == impl_->read_ahead_file_map_.end()) {
+ LOG(DEBUG) << "Could not find any TaskData for " << id;
+ return;
+ }
+
+ TaskData& task_data = it->second;
+ atrace_async_end(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead Task Scope (for File Descriptors)",
+ task_data.trace_cookie);
+
+ auto deleter = [&]() {
+ impl_->read_ahead_file_map_.erase(it);
+ };
+ auto scope_guard = android::base::make_scope_guard(deleter);
+ (void)scope_guard;
+
+ LOG(VERBOSE) << "ReadAhead (Finish)";
+
+ if (!impl_->session_manager_->DestroySession(task_data.SessionId())) {
+ LOG(WARNING) << "ReadAhead: Failed to destroy Session " << task_data.SessionId();
+ }
+}
+
+void ReadAhead::BeginTaskForSockets(const TaskId& id, int32_t trace_cookie) {
+ LOG(VERBOSE) << "BeginTaskForSockets: " << id;
+
+ // TODO: atrace.
+ android::base::Timer timer{};
+ android::base::Timer open_timer{};
+
+ int trace_file_fd_raw =
+ TEMP_FAILURE_RETRY(open(id.path.c_str(), /*flags*/O_RDONLY));
+
+ android::base::unique_fd trace_file_fd{trace_file_fd_raw};
+
+ if (!trace_file_fd.ok()) {
+ PLOG(ERROR) << "ReadAhead failed to open trace file: " << id.path;
+ return;
+ }
+
+ TaskData task_data;
+ task_data.task_id = id;
+ task_data.trace_cookie = trace_cookie;
+
+ std::shared_ptr<Session> session =
+ impl_->session_manager_->CreateSession(task_data.SessionId(),
+ /*description*/id.path,
+ trace_file_fd.get());
+ task_data.session = session;
+ CHECK(session != nullptr);
+
+ task_data.trace_cookie = ++trace_cookie;
+
+ // TODO: maybe getprop and a single line by default?
+ session->Dump(LOG_STREAM(INFO), /*multiline*/true);
+
+ impl_->read_ahead_file_map_[id.id] = std::move(task_data);
+ // FinishTask is identical, as it just destroys the session.
+}
+
+void ReadAhead::BeginTask(const TaskId& id) {
+ {
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ uint64_t now_usec = (now.tv_sec * 1000000LL + now.tv_usec);
+ LOG(DEBUG) << "BeginTask: beginning usec: " << now_usec;
+ }
+
+ int32_t trace_cookie = ++impl_->trace_cookie_;
+ atrace_async_begin(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead Task Scope (for File Descriptors)",
+ trace_cookie);
+
+ if (impl_->UseSockets()) {
+ BeginTaskForSockets(id, trace_cookie);
+ return;
+ }
+
+ LOG(VERBOSE) << "BeginTask: " << id;
+
+ // TODO: atrace.
+ android::base::Timer timer{};
+
+ // TODO: refactor this code with SessionDirect::ProcessFd ?
+ TaskData task_data;
+ task_data.task_id = id;
+ task_data.trace_cookie = trace_cookie;
+
+ ScopedFormatTrace atrace_begin_task(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead::BeginTask %s",
+ id.path.c_str());
+
+ // Include CreateSession above the Protobuf deserialization so that we can include
+ // the 'total_duration' as part of the Session dump (relevant when we use IPC mode only).
+ std::shared_ptr<Session> session =
+ impl_->session_manager_->CreateSession(task_data.SessionId(),
+ /*description*/id.path);
+
+ android::base::Timer open_timer{};
+
+ // XX: Should we rename all the 'Create' to 'Make', or rename the 'Make' to 'Create' ?
+ // Unfortunately make_unique, make_shared, etc is the standard C++ terminology.
+ serialize::ArenaPtr<serialize::proto::TraceFile> trace_file_ptr =
+ serialize::ProtobufIO::Open(id.path);
+
+ if (trace_file_ptr == nullptr) {
+ // TODO: distinguish between missing trace (this is OK, most apps wont have one)
+ // and a bad error.
+ LOG(DEBUG) << "ReadAhead could not start, missing trace file? " << id.path;
+ return;
+ }
+
+ task_data.session = session;
+ CHECK(session != nullptr);
+
+ ReadAheadKind kind = static_cast<ReadAheadKind>(GetPrefetchStrategy());
+
+ // TODO: The "Task[Id]" should probably be the one owning the trace file.
+ // When the task is fully complete, the task can be deleted and the
+ // associated arenas can go with them.
+
+ // TODO: we should probably have the file entries all be relative
+ // to the package path?
+
+ // Open every file in the trace index.
+ const serialize::proto::TraceFileIndex& index = trace_file_ptr->index();
+
+ size_t count_entries = 0;
+ {
+ ScopedFormatTrace atrace_register_file_paths(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead::RegisterFilePaths %s",
+ id.path.c_str());
+ for (const serialize::proto::TraceFileIndexEntry& index_entry : index.entries()) {
+ LOG(VERBOSE) << "ReadAhead: found file entry: " << index_entry.file_name();
+
+ if (index_entry.id() < 0) {
+ LOG(WARNING) << "ReadAhead: Skip bad TraceFileIndexEntry, negative ID not allowed: "
+ << index_entry.id();
+ continue;
+ }
+
+ size_t path_id = index_entry.id();
+ const auto& path_file_name = index_entry.file_name();
+
+ if (!session->RegisterFilePath(path_id, path_file_name)) {
+ LOG(WARNING) << "ReadAhead: Failed to register file path: " << path_file_name;
+ } else {
+ ++count_entries;
+ }
+ }
+ }
+ LOG(VERBOSE) << "ReadAhead: Registered " << count_entries << " file paths";
+ std::chrono::milliseconds open_duration_ms = open_timer.duration();
+
+ LOG(DEBUG) << "ReadAhead: Opened file&headers in " << open_duration_ms.count() << "ms";
+
+ size_t length_sum = 0;
+ size_t entry_offset = 0;
+ {
+ ScopedFormatTrace atrace_perform_read_ahead(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead::PerformReadAhead entries=%zu, path=%s",
+ count_entries,
+ id.path.c_str());
+
+ // Go through every trace entry and readahead every (file,offset,len) tuple.
+ const serialize::proto::TraceFileList& file_list = trace_file_ptr->list();
+ for (const serialize::proto::TraceFileEntry& file_entry : file_list.entries()) {
+ ++entry_offset;
+
+ if (file_entry.file_length() < 0 || file_entry.file_offset() < 0) {
+ LOG(WARNING) << "ReadAhead entry negative file length or offset, illegal: "
+ << "index_id=" << file_entry.index_id() << ", skipping";
+ continue;
+ }
+
+ // Attempt to perform readahead. This can generate more warnings dynamically.
+ if (!PerformReadAhead(session, file_entry.index_id(), kind, file_entry.file_length(), file_entry.file_offset())) {
+ // TODO: Do we need below at all? The always-on Dump already prints a % of failed entries.
+ // LOG(WARNING) << "Failed readahead, bad file length/offset in entry @ " << (entry_offset - 1);
+ }
+
+ length_sum += static_cast<size_t>(file_entry.file_length());
+ }
+ }
+
+ {
+ ScopedFormatTrace atrace_session_dump(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead Session Dump entries=%zu",
+ entry_offset);
+ // TODO: maybe getprop and a single line by default?
+ session->Dump(LOG_STREAM(INFO), /*multiline*/true);
+ }
+
+ atrace_int(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ReadAhead Bytes Length",
+ static_cast<int32_t>(length_sum));
+
+ impl_->read_ahead_file_map_[id.id] = std::move(task_data);
+
+ ReadAhead::Impl::recent_data_keeper_.RecordRecent(id, length_sum);
+}
+
+void ReadAhead::Dump(::android::Printer& printer) {
+ ReadAhead::Impl::recent_data_keeper_.Dump(printer);
+}
+
+std::optional<size_t> ReadAhead::PrefetchSizeInBytes(const std::string& file_path) {
+ serialize::ArenaPtr<serialize::proto::TraceFile> trace_file_ptr =
+ serialize::ProtobufIO::Open(file_path);
+
+ if (trace_file_ptr == nullptr) {
+ LOG(WARNING) << "PrefetchSizeInBytes: bad file at " << file_path;
+ return std::nullopt;
+ }
+
+ size_t length_sum = 0;
+ const serialize::proto::TraceFileList& file_list = trace_file_ptr->list();
+ for (const serialize::proto::TraceFileEntry& file_entry : file_list.entries()) {
+
+ if (file_entry.file_length() < 0 || file_entry.file_offset() < 0) {
+ LOG(WARNING) << "ReadAhead entry negative file length or offset, illegal: "
+ << "index_id=" << file_entry.index_id() << ", skipping";
+ continue;
+ }
+
+ length_sum += static_cast<size_t>(file_entry.file_length());
+ }
+
+ return length_sum;
+}
+
+} // namespace prefetcher
+} // namespace iorap
+
diff --git a/src/prefetcher/read_ahead.h b/src/prefetcher/read_ahead.h
new file mode 100644
index 0000000..afc6ec8
--- /dev/null
+++ b/src/prefetcher/read_ahead.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2017 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 PREFETCHER_READAHEAD_H_
+#define PREFETCHER_READAHEAD_H_
+
+#include <memory>
+#include <optional>
+
+namespace android {
+class Printer;
+} // namespace android
+
+namespace iorap {
+namespace prefetcher {
+
+struct TaskId;
+struct ReadAheadFileEntry;
+
+// Manage I/O readahead for a task.
+class ReadAhead {
+ struct Impl;
+ public:
+ // Process a task *now*. Currently will block until all readaheads have been
+ // issued for all entries in that task.
+ //
+ // Any memory mapped or file descriptors opened as a side effect must be
+ // cleaned up with #FinishTask.
+ void BeginTask(const TaskId& id);
+ // Complete a task, releasing any memory/file descriptors associated with it.
+ void FinishTask(const TaskId& id);
+
+ static void Dump(/*borrow*/::android::Printer& printer);
+
+ // Calculate the sum of file_lengths. Returns nullopt if the file path does not
+ // point to a valid compiled TraceFile.
+ static std::optional<size_t> PrefetchSizeInBytes(const std::string& file_path);
+
+ ReadAhead(bool use_sockets);
+
+ ReadAhead();
+ ~ReadAhead();
+ private:
+ void BeginTaskForSockets(const TaskId& id, int32_t trace_cookie);
+ std::unique_ptr<Impl> impl_;
+};
+
+} // namespace prefetcher
+} // namespace iorap
+
+#endif
+
diff --git a/src/prefetcher/session.cc b/src/prefetcher/session.cc
new file mode 100644
index 0000000..e998580
--- /dev/null
+++ b/src/prefetcher/session.cc
@@ -0,0 +1,724 @@
+// Copyright (C) 2019 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 "session.h"
+
+#include "prefetcher/prefetcher_daemon.h"
+#include "prefetcher/task_id.h"
+#include "serialize/arena_ptr.h"
+#include "serialize/protobuf_io.h"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <functional>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unordered_map>
+
+namespace iorap {
+namespace prefetcher {
+
+// Print per-entry details even if successful. Default-off, too spammy.
+static constexpr bool kLogVerboseReadAhead = false;
+
+std::ostream& operator<<(std::ostream& os, const Session& session) {
+ session.Dump(os, /*multiline*/false);
+ return os;
+}
+
+Session::Session() {
+}
+
+void SessionBase::Dump(std::ostream& os, bool multiline) const {
+ if (!multiline) {
+ os << "Session{";
+ os << "session_id=" << SessionId();
+ os << "}";
+ return;
+ } else {
+ os << "Session (id=" << SessionId() << ")" << std::endl;
+ return;
+ }
+}
+
+SessionBase::SessionBase(size_t session_id, std::string description)
+ : session_id_{session_id}, description_{description} {
+}
+
+std::optional<std::string_view> SessionBase::GetFilePath(size_t path_id) const {
+ auto it = path_map_.find(path_id);
+ if (it != path_map_.end()) {
+ return {it->second};
+ } else {
+ return std::nullopt;
+ }
+}
+
+bool SessionBase::RemoveFilePath(size_t path_id) {
+ auto it = path_map_.find(path_id);
+ if (it != path_map_.end()) {
+ path_map_.erase(it);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool SessionBase::InsertFilePath(size_t path_id, std::string file_path) {
+ path_map_.insert({path_id, std::move(file_path)});
+ return true;
+}
+
+bool SessionBase::ProcessFd(int fd) {
+ // Only SessionDirect has an implementation of this.
+ // TODO: Maybe add a CommandChoice::kProcessFd ? instead of kCreateFdSession?
+ LOG(FATAL) << "SessionBase::ProcessFd is not implemented";
+
+ return false;
+}
+
+//
+// Direct
+//
+
+std::ostream& operator<<(std::ostream& os, const SessionDirect::Entry& entry) {
+ os << "Entry{";
+ os << "path_id=" << entry.path_id << ",";
+ os << "kind=" << static_cast<int>(entry.kind) << ",";
+ os << "length=" << entry.length << ",";
+ os << "offset=" << entry.offset << ",";
+ os << "}";
+
+ return os;
+}
+
+// Just in case the failures are slowing down performance, turn them off.
+// static constexpr bool kLogFailures = true;
+static constexpr bool kLogFailures = false;
+
+bool SessionDirect::RegisterFilePath(size_t path_id, std::string_view file_path) {
+ std::string file_path_str{file_path}; // no c_str for string_view.
+
+ auto fd = TEMP_FAILURE_RETRY(open(file_path_str.c_str(), O_RDONLY));
+ if (fd < 0) {
+ if (kLogFailures) {
+ PLOG(ERROR) << "Failed to register file path: " << file_path << ", id=" << path_id
+ << ", open(2) failed: ";
+ }
+ fd = android::base::unique_fd{}; // mark as 'bad' descriptor.
+ }
+
+ LOG(VERBOSE) << "RegisterFilePath path_id=" << path_id << ", file_path=" << file_path_str;
+
+ if (!InsertFilePath(path_id, std::move(file_path_str))) {
+ return false;
+ }
+
+ path_fd_map_.insert(std::make_pair(path_id, std::move(fd)));
+ DCHECK(entry_list_map_[path_id].empty());
+
+ return true;
+}
+
+
+bool SessionDirect::UnregisterFilePath(size_t path_id) {
+ if (!RemoveFilePath(path_id)) {
+ return false;
+ }
+
+ { // Scoped FD reference lifetime.
+ auto maybe_fd = GetFdForPath(path_id);
+
+ DCHECK(*maybe_fd != nullptr);
+ const android::base::unique_fd& entry_fd = **maybe_fd;
+
+ auto list = entry_list_map_[path_id];
+
+ for (const EntryMapping& entry_mapping : list) {
+ ReadAheadKind kind = entry_mapping.entry.kind;
+
+ switch (kind) {
+ case ReadAheadKind::kFadvise:
+ // Nothing to do.
+ break;
+ case ReadAheadKind::kMmapLocked:
+ FALLTHROUGH_INTENDED;
+ case ReadAheadKind::kMlock:
+ // Don't do any erases in the unregister file path to avoid paying O(n^2) erase cost.
+ UnmapWithoutErase(entry_mapping);
+ break;
+ }
+ }
+ }
+
+ auto it = entry_list_map_.find(path_id);
+ auto end = entry_list_map_.end();
+ DCHECK(it != end);
+ entry_list_map_.erase(it);
+
+ // Close the FD for this file path.
+ auto fd_it = path_fd_map_.find(path_id);
+ DCHECK(fd_it != path_fd_map_.end());
+ path_fd_map_.erase(fd_it);
+
+ return true;
+}
+
+// Note: return a pointer because optional doesn't hold references directly.
+std::optional<android::base::unique_fd*> SessionDirect::GetFdForPath(size_t path_id) {
+ auto it = path_fd_map_.find(path_id);
+ if (it == path_fd_map_.end()) {
+ return std::nullopt;
+ } else {
+ return &it->second;
+ }
+}
+
+bool SessionDirect::ReadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) {
+ // Take by-reference so we can mutate list at the end.
+ auto& list = entry_list_map_[path_id];
+
+ Entry entry{path_id, kind, length, offset};
+ EntryMapping entry_mapping{entry, /*address*/nullptr, /*success*/false};
+
+ bool success = true;
+
+ auto maybe_fd = GetFdForPath(path_id);
+ if (!maybe_fd) {
+ LOG(ERROR) << "SessionDirect: Failed to find FD for path_id=" << path_id;
+ return false;
+ }
+
+ DCHECK(*maybe_fd != nullptr);
+ const android::base::unique_fd& entry_fd = **maybe_fd;
+
+ std::optional<std::string_view> file_name_opt = GetFilePath(path_id);
+ DCHECK(file_name_opt.has_value()); // if one map has it, all maps have it.
+ std::string_view file_name = *file_name_opt;
+
+ if (!entry_fd.ok()) {
+ LOG(VERBOSE) << "SessionDirect: No file descriptor for (path_id=" << path_id << ") "
+ << "path '" << file_name << "', failed to readahead entry.";
+ // Even failures get kept with success=false.
+ list.push_back(entry_mapping);
+ return false;
+ }
+
+ switch (kind) {
+ case ReadAheadKind::kFadvise:
+ if (posix_fadvise(entry_fd, offset, length, POSIX_FADV_WILLNEED) < 0) {
+ PLOG(ERROR) << "SessionDirect: Failed to fadvise entry " << file_name
+ << ", offset=" << offset << ", length=" << length;
+ success = false;
+ }
+ break;
+ case ReadAheadKind::kMmapLocked:
+ FALLTHROUGH_INTENDED;
+ case ReadAheadKind::kMlock: {
+ const bool need_mlock = kind == ReadAheadKind::kMlock;
+
+ int flags = MAP_SHARED;
+ if (!need_mlock) {
+ // MAP_LOCKED is a best-effort to lock the page. it could still be
+ // paged in later at a fault.
+ flags |= MAP_LOCKED;
+ }
+
+ entry_mapping.address =
+ mmap(/*addr*/nullptr, length, PROT_READ, flags, entry_fd, offset);
+
+ if (entry_mapping.address == nullptr) {
+ PLOG(ERROR) << "SessionDirect: Failed to mmap entry " << file_name
+ << ", offset=" << offset << ", length=" << length;
+ success = false;
+ break;
+ }
+
+ // Strong guarantee that page will be locked if mlock returns successfully.
+ if (need_mlock && mlock(entry_mapping.address, length) < 0) {
+ PLOG(ERROR) << "SessionDirect: Failed to mlock entry " << file_name
+ << ", offset=" << offset << ", length=" << length;
+ // We already have a mapping address, so we should add it to the list.
+ // However this didn't succeed 100% because the lock failed, so return false later.
+ success = false;
+ }
+ }
+ }
+
+ // Keep track of success so we know in Dump() what the number of failed entry mappings were.
+ entry_mapping.success = success;
+
+ // Keep track of this so that we can clean it up later in UnreadAhead.
+ list.push_back(entry_mapping);
+
+ if (entry_mapping.success) {
+ if (kLogVerboseReadAhead) {
+ LOG(VERBOSE) << "SessionDirect: ReadAhead for " << entry_mapping.entry;
+ }
+ } // else one of the errors above already did print.
+
+ return success;
+}
+
+bool SessionDirect::UnreadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) {
+ Entry entry{path_id, kind, length, offset};
+
+ auto list = entry_list_map_[path_id];
+ if (list.empty()) {
+ return false;
+ }
+
+ std::optional<EntryMapping> entry_mapping;
+ size_t idx = 0;
+
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (entry == list[i].entry) {
+ entry_mapping = list[i];
+ idx = 0;
+ break;
+ }
+ }
+
+ if (!entry_mapping) {
+ return false;
+ }
+
+ switch (kind) {
+ case ReadAheadKind::kFadvise:
+ // Nothing to do.
+ // TODO: maybe fadvise(RANDOM)?
+ return true;
+ case ReadAheadKind::kMmapLocked:
+ FALLTHROUGH_INTENDED;
+ case ReadAheadKind::kMlock:
+ UnmapWithoutErase(*entry_mapping);
+ return true;
+ }
+
+ list.erase(list.begin() + idx);
+
+ // FDs close only with UnregisterFilePath.
+ return true;
+}
+
+void SessionDirect::UnmapWithoutErase(const EntryMapping& entry_mapping) {
+ void* address = entry_mapping.address;
+ size_t length = entry_mapping.entry.length;
+
+ // munmap also unlocks. Do not need explicit munlock.
+ if (munmap(address, length) < 0) {
+ PLOG(WARNING) << "ReadAhead (Finish): Failed to munmap address: "
+ << address << ", length: " << length;
+ }
+
+}
+
+bool SessionDirect::ProcessFd(int fd) {
+ // TODO: the path is advisory, but it would still be cleaner to pass it separately
+ const char* fd_path = SessionDescription().c_str();
+
+ android::base::Timer open_timer{};
+ android::base::Timer total_timer{};
+
+ serialize::ArenaPtr<serialize::proto::TraceFile> trace_file_ptr =
+ serialize::ProtobufIO::Open(fd, fd_path);
+
+ if (trace_file_ptr == nullptr) {
+ LOG(ERROR) << "SessionDirect::ProcessFd failed, corrupted protobuf format? " << fd_path;
+ return false;
+ }
+
+ // TODO: maybe make it part of a kProcessFd type of command?
+ ReadAheadKind kind = ReadAheadKind::kFadvise;
+
+ // TODO: The "Task[Id]" should probably be the one owning the trace file.
+ // When the task is fully complete, the task can be deleted and the
+ // associated arenas can go with them.
+
+ // TODO: we should probably have the file entries all be relative
+ // to the package path?
+
+ // Open every file in the trace index.
+ const serialize::proto::TraceFileIndex& index = trace_file_ptr->index();
+
+ size_t count_entries = 0;
+ for (const serialize::proto::TraceFileIndexEntry& index_entry : index.entries()) {
+ LOG(VERBOSE) << "ReadAhead: found file entry: " << index_entry.file_name();
+
+ if (index_entry.id() < 0) {
+ LOG(WARNING) << "ReadAhead: Skip bad TraceFileIndexEntry, negative ID not allowed: "
+ << index_entry.id();
+ continue;
+ }
+
+ size_t path_id = index_entry.id();
+ const auto& path_file_name = index_entry.file_name();
+
+ if (!this->RegisterFilePath(path_id, path_file_name)) {
+ LOG(WARNING) << "ReadAhead: Failed to register file path: " << path_file_name;
+ ++count_entries;
+ }
+ }
+ LOG(VERBOSE) << "ReadAhead: Registered " << count_entries << " file paths";
+ std::chrono::milliseconds open_duration_ms = open_timer.duration();
+
+ LOG(DEBUG) << "ProcessFd: open+parsed headers in " << open_duration_ms.count() << "ms";
+
+ // Go through every trace entry and readahead every (file,offset,len) tuple.
+ size_t entry_offset = 0;
+ const serialize::proto::TraceFileList& file_list = trace_file_ptr->list();
+ for (const serialize::proto::TraceFileEntry& file_entry : file_list.entries()) {
+ ++entry_offset;
+
+ if (file_entry.file_length() < 0 || file_entry.file_offset() < 0) {
+ LOG(WARNING) << "ProcessFd entry negative file length or offset, illegal: "
+ << "index_id=" << file_entry.index_id() << ", skipping";
+ continue;
+ }
+
+ // Attempt to perform readahead. This can generate more warnings dynamically.
+ if (!this->ReadAhead(file_entry.index_id(),
+ kind,
+ file_entry.file_length(),
+ file_entry.file_offset())) {
+ if (kLogFailures) {
+ LOG(WARNING) << "Failed readahead, bad file length/offset in entry @ "
+ << (entry_offset - 1);
+ }
+ }
+ }
+
+ std::chrono::milliseconds total_duration_ms = total_timer.duration();
+ LOG(DEBUG) << "ProcessFd: total duration " << total_duration_ms.count() << "ms";
+
+ {
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ uint64_t now_usec = (now.tv_sec * 1000000LL + now.tv_usec);
+ LOG(DEBUG) << "ProcessFd: finishing usec: " << now_usec;
+ }
+
+ return true;
+}
+
+
+static bool IsDumpEveryEntry() {
+ // Set to 'true' to dump every single entry for debugging (multiline).
+ // Otherwise it only prints per-file-path summaries.
+ return ::android::base::GetBoolProperty("iorapd.readahead.dump_all", /*default*/false);
+}
+
+static bool IsDumpEveryPath() {
+ // Dump per-file-path (entry) stats. Defaults to on if the above property is on.
+ return ::android::base::GetBoolProperty("iorapd.readahead.dump_paths", /*default*/false);
+}
+
+void SessionDirect::Dump(std::ostream& os, bool multiline) const {
+ {
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ uint64_t now_usec = (now.tv_sec * 1000000LL + now.tv_usec);
+ LOG(DEBUG) << "SessionDirect::Dump: beginning usec: " << now_usec;
+ }
+
+ size_t path_count = entry_list_map_.size();
+
+ size_t read_ahead_entries = 0;
+ size_t read_ahead_bytes = 0;
+
+ size_t overall_entry_count = 0;
+ size_t overall_byte_count = 0;
+ for (auto it = entry_list_map_.begin(); it != entry_list_map_.end(); ++it) {
+ const auto& entry_mapping_list = it->second;
+
+ for (size_t j = 0; j < entry_mapping_list.size(); ++j) {
+ const EntryMapping& entry_mapping = entry_mapping_list[j];
+ const Entry& entry = entry_mapping.entry;
+
+ ++overall_entry_count;
+ overall_byte_count += entry.length;
+
+ if (entry_mapping.success) {
+ ++read_ahead_entries;
+ read_ahead_bytes += entry.length;
+ }
+ }
+ }
+
+ double overall_success_entry_rate =
+ read_ahead_entries * 100.0 / overall_entry_count;
+ double overall_success_byte_rate =
+ read_ahead_bytes * 100.0 / overall_byte_count;
+
+ size_t fd_count = path_fd_map_.size();
+ size_t good_fd_count = 0;
+ for (auto it = path_fd_map_.begin(); it != path_fd_map_.end(); ++it) {
+ if (it->second.ok()) {
+ ++good_fd_count;
+ }
+ }
+ double good_fd_rate = good_fd_count * 100.0 / fd_count;
+ // double bad_fd_rate = (fd_count - good_fd_count) * 1.0 / fd_count;
+
+ if (!multiline) {
+ os << "SessionDirect{";
+ os << "session_id=" << SessionId() << ",";
+
+ os << "file_paths=" << path_count << " (good: " << good_fd_rate << "),";
+ os << "read_ahead_entries=" << read_ahead_entries;
+ os << "(" << overall_success_entry_rate << "%),";
+ os << "read_ahead_bytes=" << read_ahead_bytes << "";
+ os << "(" << overall_success_byte_rate << "%),";
+ os << "timer=" << timer_.duration().count() << ",";
+
+ os << "}";
+ return;
+ } else {
+ // Always try to pay attention to these stats below.
+ // They can be signs of potential performance problems.
+ os << "Session Direct (id=" << SessionId() << ")" << std::endl;
+
+ os << " Summary: " << std::endl;
+ os << " Description = " << SessionDescription() << std::endl;
+ os << " Duration = " << timer_.duration().count() << "ms" << std::endl;
+ os << " Total File Paths=" << path_count << " (good: " << good_fd_rate << "%)" << std::endl;
+ os << " Total Entries=" << overall_entry_count;
+ os << " (good: " << overall_success_entry_rate << "%)" << std::endl;
+ os << " Total Bytes=" << overall_byte_count << "";
+ os << " (good: " << overall_success_byte_rate << "%)" << std::endl;
+ os << std::endl;
+
+ // Probably too spammy, but they could narrow down the issue for a problem in above stats.
+ if (!IsDumpEveryPath() && !IsDumpEveryEntry()) {
+ return;
+ }
+
+ for (auto it = entry_list_map_.begin(); it != entry_list_map_.end(); ++it) {
+ size_t path_id = it->first;
+ const auto& entry_mapping_list = it->second;
+
+ std::optional<std::string_view> file_path = GetFilePath(path_id);
+ os << " File Path (id=" << path_id << "): ";
+ if (file_path.has_value()) {
+ os << "'" << *file_path << "'";
+ } else {
+ os << "(nullopt)";
+ }
+
+ auto fd_it = path_fd_map_.find(path_id);
+ os << ", FD=";
+ if (fd_it != path_fd_map_.end()) {
+ const android::base::unique_fd& fd = fd_it->second;
+ os << fd.get(); // -1 for failed fd.
+ } else {
+ os << "(none)";
+ }
+ os << std::endl;
+
+ size_t total_entries = entry_mapping_list.size();
+ size_t total_bytes = 0;
+
+ size_t local_read_ahead_entries = 0;
+ size_t local_read_ahead_bytes = 0;
+ for (size_t j = 0; j < entry_mapping_list.size(); ++j) {
+ const EntryMapping& entry_mapping = entry_mapping_list[j];
+ const Entry& entry = entry_mapping.entry;
+
+ total_bytes += entry.length;
+
+ // Sidenote: Bad FDs will have 100% failed mappings.
+ // Good FDs may sometimes have failed mappings.
+ if (entry_mapping.success) {
+ ++local_read_ahead_entries;
+ local_read_ahead_bytes += entry.length;
+ }
+
+ if (IsDumpEveryEntry()) {
+ os << " Entry " << j << " details:" << std::endl;
+ os << " " << entry << std::endl;
+ os << " Mapping " << (entry_mapping.success ? "Succeeded" : "Failed")
+ << ", Address " << entry_mapping.address << std::endl;
+ }
+ }
+
+ double entry_success_rate = local_read_ahead_entries * 100.0 / total_entries;
+ double bytes_success_rate = local_read_ahead_bytes * 100.0 / total_bytes;
+
+ double entry_failure_rate = (total_entries - local_read_ahead_entries) * 100.0 / total_entries;
+ double bytes_failure_rate = (total_bytes - local_read_ahead_bytes) * 100.0 / total_bytes;
+
+ os << " Successful: Entries=" << local_read_ahead_entries
+ << " (" << entry_success_rate << "%)"
+ << ", Bytes=" << local_read_ahead_bytes
+ << " (" << bytes_success_rate << "%)"
+ << std::endl;
+ os << " Failed: Entries=" << (total_entries - local_read_ahead_entries)
+ << " (" << entry_failure_rate << "%)"
+ << ", Bytes=" << (total_bytes - local_read_ahead_bytes)
+ << " (" << bytes_failure_rate << "%)"
+ << std::endl;
+ os << " Total: Entries=" << total_entries
+ << ", Bytes=" << total_bytes
+ << std::endl;
+ }
+
+ return;
+ }
+}
+
+SessionDirect::~SessionDirect() {
+ for (auto it = entry_list_map_.begin(); it != entry_list_map_.end();) {
+ size_t path_id = it->first;
+
+ ++it; // the iterator is removed in the following Unregister method.
+ UnregisterFilePath(path_id);
+ }
+}
+
+//
+// Indirect
+//
+
+SessionIndirect::SessionIndirect(size_t session_id,
+ std::string description,
+ std::shared_ptr<PrefetcherDaemon> daemon,
+ bool send_command)
+ : SessionBase{session_id, description},
+ daemon_{daemon} {
+ // Don't do anything in e.g. subclasses.
+ if (!send_command) {
+ return;
+ }
+
+ Command cmd{};
+ cmd.choice = CommandChoice::kCreateSession;
+ cmd.session_id = session_id;
+ cmd.file_path = description;
+
+ LOG(VERBOSE) << "SessionIndirect: " << cmd;
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(FATAL) << "SessionIndirect: Failure to create session " << session_id
+ << ", description: " << description;
+ }
+}
+
+SessionIndirect::~SessionIndirect() {
+ Command cmd{};
+ cmd.choice = CommandChoice::kDestroySession;
+ cmd.session_id = SessionId();
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(WARNING) << "SessionIndirect: Failure to destroy session " << SessionId()
+ << ", description: " << SessionDescription();
+ }
+}
+
+void SessionIndirect::Dump(std::ostream& os, bool multiline) const {
+ // SessionBase::Dump(os, multiline);
+ // TODO: does having the local dump do anything for us?
+
+ Command cmd{};
+ cmd.choice = CommandChoice::kDumpSession;
+ cmd.session_id = SessionId();
+
+ daemon_->SendCommand(cmd);
+}
+
+bool SessionIndirect::RegisterFilePath(size_t path_id, std::string_view file_path) {
+ Command cmd{};
+ cmd.choice = CommandChoice::kRegisterFilePath;
+ cmd.session_id = SessionId();
+ cmd.id = path_id;
+ cmd.file_path = file_path;
+
+ return daemon_->SendCommand(cmd);
+}
+
+bool SessionIndirect::UnregisterFilePath(size_t path_id) {
+ Command cmd{};
+ cmd.choice = CommandChoice::kUnregisterFilePath;
+ cmd.session_id = SessionId();
+ cmd.id = path_id;
+
+ return daemon_->SendCommand(cmd);
+}
+bool SessionIndirect::ReadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) {
+ Command cmd{};
+ cmd.choice = CommandChoice::kReadAhead;
+ cmd.session_id = SessionId();
+ cmd.id = path_id;
+ cmd.read_ahead_kind = kind;
+ cmd.length = length;
+ cmd.offset = offset;
+
+ return daemon_->SendCommand(cmd);
+}
+
+bool SessionIndirect::UnreadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) {
+ LOG(WARNING) << "UnreadAhead: command not implemented yet";
+ return true;
+}
+
+//
+// IndirectSocket
+//
+
+SessionIndirectSocket::SessionIndirectSocket(size_t session_id,
+ int fd,
+ std::string description,
+ std::shared_ptr<PrefetcherDaemon> daemon)
+ : SessionIndirect{session_id, description, daemon, /*send_command*/false} {
+ // TODO: all of the WriteCommand etc in the daemon.
+ Command cmd{};
+ cmd.choice = CommandChoice::kCreateFdSession;
+ cmd.fd = fd;
+ cmd.session_id = session_id;
+ cmd.file_path = description;
+
+ LOG(VERBOSE) << "SessionIndirectSocket: " << cmd;
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(FATAL) << "SessionIndirectSocket: Failure to create session " << session_id
+ << ", description: " << description;
+ }
+
+ // This goes into the SessionDirect ctor + SessionDirect::ProcessFd
+ // as implemented in PrefetcherDaemon::ReceiveCommand
+}
+
+SessionIndirectSocket::~SessionIndirectSocket() {
+}
+
+} // namespace prefetcher
+} // namespace iorap
diff --git a/src/prefetcher/session.h b/src/prefetcher/session.h
new file mode 100644
index 0000000..a4a9e6b
--- /dev/null
+++ b/src/prefetcher/session.h
@@ -0,0 +1,236 @@
+// Copyright (C) 2019 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 PREFETCHER_SESSION_H_
+#define PREFETCHER_SESSION_H_
+
+#include <android-base/chrono_utils.h>
+#include <android-base/unique_fd.h>
+
+#include <optional>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+namespace iorap {
+namespace prefetcher {
+
+#ifndef READ_AHEAD_KIND
+#define READ_AHEAD_KIND 1
+enum class ReadAheadKind : uint32_t {
+ kFadvise = 0,
+ kMmapLocked = 1,
+ kMlock = 2,
+};
+#endif
+
+class Session {
+ public:
+ virtual bool RegisterFilePath(size_t path_id, std::string_view file_path) = 0;
+ virtual bool UnregisterFilePath(size_t path_id) = 0;
+
+ // Immediately perform a readahead now.
+ // Fadvise: the readahead will have been queued by the kernel.
+ // MmapLocked/Mlock: the memory is pinned by the requested process.
+ virtual bool ReadAhead(size_t path_id, ReadAheadKind kind, size_t length, size_t offset) = 0;
+
+ // Cancels a readahead previously done.
+ // The length/offset should match the call of ReadAhead.
+ virtual bool UnreadAhead(size_t path_id, ReadAheadKind kind, size_t length, size_t offset) = 0;
+
+ // Multi-line detailed dump, e.g. for dumpsys.
+ // Single-line summary dump, e.g. for logcat.
+ virtual void Dump(std::ostream& os, bool multiline) const = 0;
+
+ // Process the FD for kCreateFdSession.
+ // Assumes there's a compiled_trace.pb at the fd, calling this function
+ // will immediately process it and execute any read-aheads.
+ //
+ // FD is borrowed only for the duration of the function call.
+ virtual bool ProcessFd(int fd) = 0;
+
+ // Get the session ID associated with this session.
+ // Session IDs are distinct, they are not used for new sessions.
+ virtual size_t SessionId() const = 0;
+
+ // Get this session's description.
+ // Only useful for logging/dumping.
+ virtual const std::string& SessionDescription() const = 0;
+
+ // Implicitly unregister any remaining file paths.
+ // All read-aheads are also cancelled.
+ virtual ~Session() {}
+
+ protected:
+ Session();
+};
+
+// Single-line summary dump of Session.
+std::ostream& operator<<(std::ostream&os, const Session& session);
+
+class SessionBase : public Session {
+ public:
+ virtual void Dump(std::ostream& os, bool multiline) const override;
+ virtual ~SessionBase() {}
+
+ virtual size_t SessionId() const override {
+ return session_id_;
+ }
+
+ virtual const std::string& SessionDescription() const override {
+ return description_;
+ }
+
+ virtual bool ProcessFd(int fd) override;
+
+ protected:
+ SessionBase(size_t session_id, std::string description);
+ std::optional<std::string_view> GetFilePath(size_t path_id) const;
+ bool RemoveFilePath(size_t path_id);
+ bool InsertFilePath(size_t path_id, std::string file_path);
+
+ android::base::Timer timer_{};
+ private:
+ // Note: Store filename for easier debugging and for dumping.
+ std::unordered_map</*path_id*/size_t, std::string> path_map_;
+ size_t session_id_;
+ std::string description_;
+};
+
+// In-process session.
+class SessionDirect : public SessionBase {
+ public:
+ virtual bool RegisterFilePath(size_t path_id, std::string_view file_path) override;
+
+ virtual bool UnregisterFilePath(size_t path_id) override;
+ virtual bool ReadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset);
+
+ virtual bool UnreadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) override;
+
+ virtual bool ProcessFd(int fd) override;
+
+ virtual void Dump(std::ostream& os, bool multiline) const override;
+
+ virtual ~SessionDirect();
+
+ SessionDirect(size_t session_id, std::string description)
+ : SessionBase{session_id, std::move(description)} {
+ }
+ protected:
+ struct Entry {
+ size_t path_id;
+ ReadAheadKind kind;
+ size_t length;
+ size_t offset;
+
+ constexpr bool operator==(const Entry& other) const {
+ return path_id == other.path_id &&
+ kind == other.kind &&
+ length == other.length &&
+ offset == other.offset;
+ }
+ constexpr bool operator!=(const Entry& other) const {
+ return !(*this == other);
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const Entry& entry);
+ };
+
+ struct EntryMapping {
+ Entry entry;
+ void* address;
+ bool success;
+ };
+
+ // bool EntryReadAhead(const Entry& entry) const;
+ // bool EntryUnReadAhead(const Entry& entry) const;
+
+ void UnmapWithoutErase(const EntryMapping& entry_mapping);
+ std::optional<android::base::unique_fd*> GetFdForPath(size_t path_id);
+
+ private:
+ std::unordered_map</*path_id*/size_t, std::vector<EntryMapping>> entry_list_map_;
+ std::unordered_map</*path_id*/size_t, android::base::unique_fd> path_fd_map_;
+
+ public:
+ friend std::ostream& operator<<(std::ostream& os, const SessionDirect::Entry& entry);
+};
+
+std::ostream& operator<<(std::ostream& os, const SessionDirect::Entry& entry);
+
+class PrefetcherDaemon;
+
+// Out-of-process session. Requires prefetcher daemon.
+class SessionIndirect : public SessionBase {
+ public:
+ virtual bool RegisterFilePath(size_t path_id, std::string_view file_path) override;
+
+ virtual bool UnregisterFilePath(size_t path_id) override;
+ virtual bool ReadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) override;
+
+ virtual bool UnreadAhead(size_t path_id,
+ ReadAheadKind kind,
+ size_t length,
+ size_t offset) override;
+
+ virtual void Dump(std::ostream& os, bool multiline) const override;
+
+ // Creates a new session indirectly.
+ // Writes to daemon the new session command.
+ SessionIndirect(size_t session_id,
+ std::string description,
+ std::shared_ptr<PrefetcherDaemon> daemon,
+ bool send_command = true);
+
+ // Destroys the current session.
+ // Writes to daemon that the session is to be destroyed.
+ virtual ~SessionIndirect();
+
+ protected:
+ std::shared_ptr<PrefetcherDaemon> daemon_;
+};
+
+// Out-of-process session. Requires prefetcher daemon.
+class SessionIndirectSocket : public SessionIndirect {
+ public:
+ // Creates a new session indirectly.
+ // Writes to daemon the new session command.
+ SessionIndirectSocket(size_t session_id,
+ int fd,
+ std::string description,
+ std::shared_ptr<PrefetcherDaemon> daemon);
+ // Destroys the current session.
+ // Writes to daemon that the session is to be destroyed.
+ virtual ~SessionIndirectSocket();
+
+ private:
+};
+
+
+} // namespace prefetcher
+} // namespace iorap
+
+#endif
+
diff --git a/src/prefetcher/session_manager.cc b/src/prefetcher/session_manager.cc
new file mode 100644
index 0000000..d6fab1f
--- /dev/null
+++ b/src/prefetcher/session_manager.cc
@@ -0,0 +1,281 @@
+// Copyright (C) 2019 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 "session_manager.h"
+
+#include "prefetcher/prefetcher_daemon.h"
+#include "prefetcher/session.h"
+#include "prefetcher/task_id.h"
+#include "serialize/arena_ptr.h"
+#include "serialize/protobuf_io.h"
+
+#include <android-base/logging.h>
+#include <android-base/chrono_utils.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <functional>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unordered_map>
+
+namespace iorap {
+namespace prefetcher {
+
+std::ostream& operator<<(std::ostream& os, const SessionManager& manager) {
+ manager.Dump(os, /*multiline*/false);
+ return os;
+}
+
+SessionManager::SessionManager() {
+}
+
+class SessionManagerBase : public SessionManager {
+ public:
+ virtual void Dump(std::ostream& os, bool multiline) const {
+ if (!multiline) {
+ os << "SessionManager{";
+
+ os << "sessions=[";
+ for (auto it = sessions_map_.begin();
+ it != sessions_map_.end();
+ ++it) {
+ os << "(" << it->second.description << ") ";
+ it->second.session->Dump(os, /*multiline*/false);
+ }
+ os << "]";
+ return;
+ }
+
+ os << "SessionManager (session count = " << sessions_map_.size() << "):" << std::endl;
+ os << std::endl;
+
+ for (auto it = sessions_map_.begin();
+ it != sessions_map_.end();
+ ++it) {
+ os << "Description: " << it->second.description << std::endl;
+ it->second.session->Dump(os, /*multiline*/true);
+ }
+
+ // TODO: indentations? Use this pseudo line break for the time being.
+ os << "--------------------------------" << std::endl;
+ }
+
+ virtual ~SessionManagerBase() {}
+
+ virtual std::shared_ptr<Session> FindSession(size_t session_id) const override {
+ auto it = sessions_map_.find(session_id);
+ if (it != sessions_map_.end()) {
+ DCHECK_EQ(session_id, it->second.SessionId());
+ return it->second.session;
+ } else {
+ return nullptr;
+ }
+ }
+
+ virtual bool DestroySession(size_t session_id) override {
+ auto it = sessions_map_.find(session_id);
+ if (it != sessions_map_.end()) {
+ sessions_map_.erase(it);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected:
+ void InsertNewSession(std::shared_ptr<Session> session, std::string description) {
+ DCHECK(!FindSession(session->SessionId())) << "session cannot already exist";
+
+ size_t session_id = session->SessionId();
+
+ SessionData data;
+ data.session = std::move(session);
+ data.description = std::move(description);
+
+ sessions_map_.insert({session_id, std::move(data)});
+ }
+
+ private:
+ struct SessionData {
+ std::shared_ptr<Session> session;
+ std::string description;
+
+ size_t SessionId() const {
+ return session->SessionId();
+ }
+ };
+
+ std::unordered_map</*session_id*/size_t, SessionData> sessions_map_;
+};
+
+class SessionManagerDirect : public SessionManagerBase {
+ public:
+ virtual std::shared_ptr<Session> CreateSession(size_t session_id,
+ std::string description) override {
+ LOG(VERBOSE) << "CreateSessionDirect id=" << session_id << ", description=" << description;
+
+ std::shared_ptr<Session> session =
+ std::static_pointer_cast<Session>(std::make_shared<SessionDirect>(session_id,
+ description));
+ DCHECK(FindSession(session_id) == nullptr);
+ InsertNewSession(session, std::move(description));
+ return session;
+ }
+
+ SessionManagerDirect() {
+ // Intentionally left empty.
+ }
+
+ private:
+};
+
+
+class SessionManagerIndirect : public SessionManagerBase {
+ public:
+ virtual std::shared_ptr<Session> CreateSession(size_t session_id,
+ std::string description) override {
+ LOG(VERBOSE) << "CreateSessionIndirect id=" << session_id << ", description=" << description;
+
+ std::shared_ptr<Session> session =
+ std::static_pointer_cast<Session>(std::make_shared<SessionIndirect>(session_id,
+ description,
+ daemon_));
+ InsertNewSession(session, description);
+ return session;
+ }
+
+ SessionManagerIndirect() : daemon_{std::make_shared<PrefetcherDaemon>()} {
+ //StartViaFork etc.
+ // TODO: also expose a 'MainLoop(...) -> daemon::Main(..)' somehow in the base interface.
+ auto params = daemon_->StartPipesViaFork();
+ if (!params) {
+ LOG(FATAL) << "Failed to fork+exec iorap.prefetcherd";
+ }
+ }
+
+ virtual ~SessionManagerIndirect() {
+ Command cmd{};
+ cmd.choice = CommandChoice::kExit;
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(FATAL) << "Failed to nicely exit iorap.prefetcherd";
+ }
+ }
+
+ virtual void Dump(std::ostream& os, bool multiline) const override {
+ Command cmd{};
+ cmd.choice = CommandChoice::kDumpEverything;
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(ERROR) << "Failed to transmit kDumpEverything to iorap.prefetcherd";
+ }
+ }
+
+
+ private:
+ // No lifetime cycle: PrefetcherDaemon only has a SessionManagerDirect in it.
+ std::shared_ptr<PrefetcherDaemon> daemon_;
+};
+
+class SessionManagerIndirectSocket : public SessionManagerBase {
+ public:
+ virtual std::shared_ptr<Session> CreateSession(size_t session_id,
+ std::string description) override {
+ DCHECK(false) << "not supposed to create a regular session for Socket";
+
+ LOG(VERBOSE) << "CreateSessionIndirect id=" << session_id << ", description=" << description;
+
+ std::shared_ptr<Session> session =
+ std::static_pointer_cast<Session>(std::make_shared<SessionIndirect>(session_id,
+ description,
+ daemon_));
+ InsertNewSession(session, description);
+ return session;
+ }
+
+ virtual std::shared_ptr<Session> CreateSession(size_t session_id,
+ std::string description,
+ std::optional<int> fd) override {
+ CHECK(fd.has_value());
+ LOG(VERBOSE) << "CreateSessionIndirectSocket id=" << session_id
+ << ", description=" << description
+ << ", fd=" << *fd;
+
+ std::shared_ptr<Session> session =
+ std::static_pointer_cast<Session>(std::make_shared<SessionIndirectSocket>(session_id,
+ *fd,
+ description,
+ daemon_));
+ InsertNewSession(session, description);
+ return session;
+ }
+
+ SessionManagerIndirectSocket() : daemon_{std::make_shared<PrefetcherDaemon>()} {
+ auto params = daemon_->StartSocketViaFork();
+ if (!params) {
+ LOG(FATAL) << "Failed to fork+exec iorap.prefetcherd";
+ }
+ }
+
+ virtual ~SessionManagerIndirectSocket() {
+ Command cmd{};
+ cmd.choice = CommandChoice::kExit;
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(FATAL) << "Failed to nicely exit iorap.prefetcherd";
+ }
+ }
+
+ virtual void Dump(std::ostream& os, bool multiline) const override {
+ Command cmd{};
+ cmd.choice = CommandChoice::kDumpEverything;
+
+ if (!daemon_->SendCommand(cmd)) {
+ LOG(ERROR) << "Failed to transmit kDumpEverything to iorap.prefetcherd";
+ }
+ }
+
+
+ private:
+ // No lifetime cycle: PrefetcherDaemon only has a SessionManagerDirect in it.
+ std::shared_ptr<PrefetcherDaemon> daemon_;
+};
+
+std::unique_ptr<SessionManager> SessionManager::CreateManager(SessionKind kind) {
+ LOG(VERBOSE) << "SessionManager::CreateManager kind=" << kind;
+
+ switch (kind) {
+ case SessionKind::kInProcessDirect: {
+ SessionManager* ptr = new SessionManagerDirect();
+ return std::unique_ptr<SessionManager>{ptr};
+ }
+ case SessionKind::kOutOfProcessIpc: {
+ SessionManager* ptr = new SessionManagerIndirect();
+ return std::unique_ptr<SessionManager>{ptr};
+ }
+ case SessionKind::kOutOfProcessSocket: {
+ SessionManager* ptr = new SessionManagerIndirectSocket();
+ return std::unique_ptr<SessionManager>{ptr};
+ }
+ default: {
+ LOG(FATAL) << "Invalid session kind: " << static_cast<int>(kind);
+ break;
+ }
+ }
+}
+
+} // namespace prefetcher
+} // namespace iorap
diff --git a/src/prefetcher/session_manager.h b/src/prefetcher/session_manager.h
new file mode 100644
index 0000000..45120d5
--- /dev/null
+++ b/src/prefetcher/session_manager.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2019 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 PREFETCHER_SESSION_MANAGER_H_
+#define PREFETCHER_SESSION_MANAGER_H_
+
+#include <optional>
+#include <ostream>
+#include <memory>
+
+namespace iorap {
+namespace prefetcher {
+
+class Session;
+
+enum class SessionKind : uint32_t {
+ kInProcessDirect,
+ kOutOfProcessIpc,
+ kOutOfProcessSocket,
+};
+
+inline std::ostream& operator<<(std::ostream& os, SessionKind kind) {
+ if (kind == SessionKind::kInProcessDirect) {
+ os << "kInProcessDirect";
+ } else if (kind == SessionKind::kOutOfProcessIpc) {
+ os << "kOutOfProcessIpc";
+ } else if (kind == SessionKind::kOutOfProcessSocket) {
+ os << "kOutOfProcessSocket";
+ } else {
+ os << "(invalid)";
+ }
+ return os;
+}
+
+class SessionManager {
+ public:
+ static std::unique_ptr<SessionManager> CreateManager(SessionKind kind);
+
+ // Create a new session. The description is used by Dump.
+ // Manager maintains a strong ref to this session, so DestroySession must also
+ // be called prior to all refs dropping to 0.
+ virtual std::shared_ptr<Session> CreateSession(size_t session_id,
+ std::string description) = 0;
+
+ // Create a new session. The description is used by Dump.
+ // Manager maintains a strong ref to this session, so DestroySession must also
+ // be called prior to all refs dropping to 0.
+ virtual std::shared_ptr<Session> CreateSession(size_t session_id,
+ std::string description,
+ std::optional<int> fd) {
+ return CreateSession(session_id, description);
+ }
+
+ // Look up an existing session that was already created.
+ // Returns null if there is no such session.
+ virtual std::shared_ptr<Session> FindSession(size_t session_id) const = 0;
+
+ // Drop all manager references to an existing session.
+ // Returns false if the session does not exist already.
+ virtual bool DestroySession(size_t session_id) = 0;
+
+ // Multi-line detailed dump, e.g. for dumpsys.
+ // Single-line summary dump, e.g. for logcat.
+ virtual void Dump(std::ostream& os, bool multiline) const = 0;
+
+ // Note: session lifetime is tied to manager. The manager has strong pointers to sessions.
+ virtual ~SessionManager() {}
+
+ protected:
+ SessionManager();
+};
+
+// Single-line summary dump of Session.
+std::ostream& operator<<(std::ostream&os, const SessionManager& session);
+
+} // namespace prefetcher
+} // namespace iorap
+
+#endif
+
diff --git a/src/prefetcher/task_id.h b/src/prefetcher/task_id.h
new file mode 100644
index 0000000..dc26954
--- /dev/null
+++ b/src/prefetcher/task_id.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 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 PREFETCHER_TASK_ID_H_
+#define PREFETCHER_TASK_ID_H_
+
+#include <ostream>
+#include <string>
+
+namespace iorap {
+namespace prefetcher {
+
+struct TaskId {
+ size_t id; // Unique monotonically increasing ID.
+ std::string path; // File path to the trace file.
+
+ friend std::ostream& operator<<(std::ostream& os, const TaskId& task_id) {
+ os << "TaskId { id: " << task_id.id << ", path: " << task_id.path << "}";
+ return os;
+ }
+
+};
+
+} // namespace prefetcher
+} // namespace iorap
+
+#endif
+
diff --git a/src/serialize/TraceFile.proto b/src/serialize/TraceFile.proto
new file mode 100644
index 0000000..fc72d0d
--- /dev/null
+++ b/src/serialize/TraceFile.proto
@@ -0,0 +1,47 @@
+// Copyright (C) 2017 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.
+
+syntax = "proto2"; // use required fields, which aren't in proto3.
+
+package iorap.serialize.proto; // C++ namespace iorap::serialize::proto
+option java_package = "com.google.android.iorap";
+option optimize_for = LITE_RUNTIME;
+
+// TODO: should these fields be 'packed' for "smaller encoding" ?
+
+message TraceFile {
+ required TraceFileIndex index = 1;
+ required TraceFileList list = 2;
+}
+
+message TraceFileIndex {
+ repeated TraceFileIndexEntry entries = 1;
+}
+
+message TraceFileIndexEntry {
+ required int64 id = 1;
+ required string file_name = 2;
+}
+
+message TraceFileList {
+ repeated TraceFileEntry entries = 1;
+}
+
+message TraceFileEntry {
+ required int64 index_id = 1;
+ required int64 file_offset = 2;
+ required int64 file_length = 3;
+}
+
+// XX: use nested messages? \ No newline at end of file
diff --git a/src/serialize/arena_ptr.h b/src/serialize/arena_ptr.h
new file mode 100644
index 0000000..d8bea26
--- /dev/null
+++ b/src/serialize/arena_ptr.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2017 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 SERIALIZE_ARENA_PTR_H_
+#define SERIALIZE_ARENA_PTR_H_
+
+#include <google/protobuf/arena.h>
+#include <memory>
+
+namespace iorap {
+namespace serialize {
+
+/**
+ * @file
+ *
+ * Helpers for protobuf arena allocators. We use smart pointers
+ * with an arena embedded inside of them to avoid caring about the
+ * arena in other parts of libiorap.
+ */
+
+// Arena-managed objects must not be deleted manually.
+// When the Arena goes out of scope, it cleans everything up itself.
+template <typename T>
+void DoNotDelete(T*) {}
+
+template <typename T, typename Base = std::unique_ptr<T, decltype(&DoNotDelete<T>)>>
+struct ArenaPtr : public Base {
+ template <typename... Args>
+ static ArenaPtr<T> Make(Args&& ... args) {
+ ArenaPtr<T> arena_ptr(nullptr);
+ arena_ptr.reset(google::protobuf::Arena::Create<T>(arena_ptr.arena_.get(),
+ std::forward<Args>(args)...));
+ return arena_ptr;
+ }
+
+ ArenaPtr(std::nullptr_t) : Base(nullptr, &DoNotDelete<T>) {} // NOLINT explicit.
+
+ private:
+ // Use a unique_ptr because Arena doesn't support move semantics.
+ std::unique_ptr<google::protobuf::Arena> arena_{new google::protobuf::Arena{}};
+};
+
+template <typename T, typename Base = std::shared_ptr<T>>
+struct ArenaSharedPtr : public Base {
+ template <typename... Args>
+ static ArenaSharedPtr<T> Make(Args&& ... args) {
+ ArenaSharedPtr<T> arena_ptr(nullptr, &DoNotDelete<T>);
+ arena_ptr.reset(google::protobuf::Arena::Create<T>(arena_ptr.arena_.get(),
+ std::forward<Args>(args)...));
+ return arena_ptr;
+ }
+
+ ArenaSharedPtr() = default;
+ template <typename Deleter>
+ ArenaSharedPtr(std::nullptr_t, Deleter d) : Base(nullptr, d) {} // NOLINT explicit.
+
+ private:
+ std::shared_ptr<google::protobuf::Arena> arena_{new google::protobuf::Arena{}};
+};
+
+} // namespace serialize
+} // namespace iorap
+
+#endif
+
diff --git a/src/serialize/protobuf_io.cc b/src/serialize/protobuf_io.cc
new file mode 100644
index 0000000..1b6420f
--- /dev/null
+++ b/src/serialize/protobuf_io.cc
@@ -0,0 +1,173 @@
+// Copyright (C) 2017 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 "protobuf_io.h"
+
+#include "common/trace.h"
+#include "serialize/arena_ptr.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Trace.h>
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "system/iorap/src/serialize/TraceFile.pb.h"
+
+namespace iorap {
+namespace serialize {
+
+ArenaPtr<proto::TraceFile> ProtobufIO::Open(std::string file_path) {
+ // TODO: file a bug about this.
+ // Note: can't use {} here, clang think it's narrowing from long->int.
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open(file_path.c_str(), O_RDONLY)));
+ if (fd.get() < 0) {
+ PLOG(DEBUG) << "ProtobufIO: open failed: " << file_path;
+ return nullptr;
+ }
+
+ return Open(fd.get(), file_path.c_str());
+}
+
+ArenaPtr<proto::TraceFile> ProtobufIO::Open(int fd, const char* file_path) {
+
+ ScopedFormatTrace atrace_protobuf_io_open(ATRACE_TAG_ACTIVITY_MANAGER,
+ "ProtobufIO::Open %s",
+ file_path);
+ android::base::Timer timer{};
+
+ struct stat buf;
+ if (fstat(fd, /*out*/&buf) < 0) {
+ PLOG(ERROR) << "ProtobufIO: open error, fstat failed: " << file_path;
+ return nullptr;
+ }
+ // XX: off64_t for stat::st_size ?
+
+ // Using the mmap appears to be the only way to do zero-copy with protobuf lite.
+ void* data = mmap(/*addr*/nullptr,
+ buf.st_size,
+ PROT_READ, MAP_SHARED | MAP_POPULATE,
+ fd,
+ /*offset*/0);
+ if (data == nullptr) {
+ PLOG(ERROR) << "ProtobufIO: open error, mmap failed: " << file_path;
+ return nullptr;
+ }
+
+ ArenaPtr<proto::TraceFile> protobuf_trace_file = ArenaPtr<proto::TraceFile>::Make();
+ if (protobuf_trace_file == nullptr) {
+ LOG(ERROR) << "ProtobufIO: open error, failed to create arena: " << file_path;
+ return nullptr;
+ }
+
+ google::protobuf::io::ArrayInputStream protobuf_input_stream{data, static_cast<int>(buf.st_size)};
+ if (!protobuf_trace_file->ParseFromZeroCopyStream(/*in*/&protobuf_input_stream)) {
+ // XX: Does protobuf on android already have the right LogHandler ?
+ LOG(ERROR) << "ProtobufIO: open error, protobuf parsing failed: " << file_path;
+ return nullptr;
+ }
+
+ if (munmap(data, buf.st_size) < 0) {
+ PLOG(WARNING) << "ProtobufIO: open problem, munmap failed, possibly memory leak? "
+ << file_path;
+ }
+
+ LOG(VERBOSE) << "ProtobufIO: open succeeded: " << file_path << ", duration: " << timer;
+ return protobuf_trace_file;
+}
+
+iorap::expected<size_t /*bytes written*/, int /*errno*/> ProtobufIO::WriteFully(
+ const ::google::protobuf::MessageLite& message,
+ std::string_view file_path) {
+
+ std::string str{file_path};
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(
+ ::open(str.c_str(),
+ O_CREAT | O_TRUNC | O_RDWR,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP))); // ugo: rw-rw----
+ if (fd.get() < 0) {
+ int err = errno;
+ PLOG(ERROR) << "ProtobufIO: open failed: " << file_path;
+ return unexpected{err};
+ }
+
+ return WriteFully(message, fd.get(), file_path);
+}
+
+iorap::expected<size_t /*bytes written*/, int /*errno*/> ProtobufIO::WriteFully(
+ const ::google::protobuf::MessageLite& message,
+ int fd,
+ std::string_view file_path) {
+
+ int byte_size = message.ByteSize();
+ if (byte_size < 0) {
+ DCHECK(false) << "Invalid protobuf size: " << byte_size;
+ LOG(ERROR) << "ProtobufIO: Invalid protobuf size: " << byte_size;
+ return unexpected{EDOM};
+ }
+ size_t serialized_size = static_cast<size_t>(byte_size);
+
+ // Change the file to be exactly the length of the protobuf.
+ if (ftruncate(fd, static_cast<off_t>(serialized_size)) < 0) {
+ int err = errno;
+ PLOG(ERROR) << "ProtobufIO: ftruncate (size=" << serialized_size << ") failed";
+ return unexpected{err};
+ }
+
+ // Using the mmap appears to be the only way to do zero-copy with protobuf lite.
+ void* data = mmap(/*addr*/nullptr,
+ serialized_size,
+ PROT_WRITE,
+ MAP_SHARED,
+ fd,
+ /*offset*/0);
+ if (data == nullptr) {
+ int err = errno;
+ PLOG(ERROR) << "ProtobufIO: mmap failed: " << file_path;
+ return unexpected{err};
+ }
+
+ // Zero-copy write from protobuf to file via memory-map.
+ ::google::protobuf::io::ArrayOutputStream output_stream{data, byte_size};
+ if (!message.SerializeToZeroCopyStream(/*inout*/&output_stream)) {
+ // This should never happen since we pre-allocated the file and memory map to be large
+ // enough to store the full protobuf.
+ DCHECK(false) << "ProtobufIO:: SerializeToZeroCopyStream failed despite precalculating size";
+ LOG(ERROR) << "ProtobufIO: SerializeToZeroCopyStream failed";
+ return unexpected{EXFULL};
+ }
+
+ // Guarantee that changes are written back prior to munmap.
+ if (msync(data, static_cast<size_t>(serialized_size), MS_SYNC) < 0) {
+ int err = errno;
+ PLOG(ERROR) << "ProtobufIO: msync failed";
+ return unexpected{err};
+ }
+
+ if (munmap(data, serialized_size) < 0) {
+ PLOG(WARNING) << "ProtobufIO: munmap failed, possibly memory leak? "
+ << file_path;
+ }
+
+ return serialized_size;
+}
+
+} // namespace serialize
+} // namespace iorap
+
diff --git a/src/serialize/protobuf_io.h b/src/serialize/protobuf_io.h
new file mode 100644
index 0000000..1092fd7
--- /dev/null
+++ b/src/serialize/protobuf_io.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2017 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 SERIALIZE_PROTOBUF_IO_H_
+#define SERIALIZE_PROTOBUF_IO_H_
+
+#include "common/expected.h"
+#include "serialize/arena_ptr.h"
+#include "system/iorap/src/serialize/TraceFile.pb.h"
+
+#include <string>
+#include <string_view>
+
+namespace iorap {
+namespace serialize {
+
+// XX: either the namespace should be called pb|proto[buf]
+// or we should hide the protobuf-ness from the names and call this class "IO" , "Reader", etc?
+// although an obvious name might be the "OpenFactory" or "ProtobufFacade" that reads too much
+// like a bad joke.
+
+// Helpers to read a TraceFile protobuf from a file [descriptor].
+class ProtobufIO {
+ public:
+ // XX: proto::TraceFile seems annoying, maybe just serialize::TraceFile ?
+
+ // Open the protobuf associated at the filepath. Returns null on failure.
+ static ArenaPtr<proto::TraceFile> Open(std::string file_path);
+ // Open the protobuf from the file descriptor. Returns null on failure.
+ static ArenaPtr<proto::TraceFile> Open(int fd, const char* file_path = "<unknown>");
+
+ // Save the protobuf by overwriting the file at file_path.
+ // The file state is indeterminate at failure.
+ // Returns # of bytes written out on success, otherwise the errno value.
+ static iorap::expected<size_t /*bytes written*/, int /*errno*/> WriteFully(
+ const ::google::protobuf::MessageLite& message,
+ std::string_view file_path);
+ // Save the protobuf by truncating the file already open at 'fd'.
+ // The file state is indeterminate at failure.
+ // Returns # of bytes written out on success, otherwise the errno value.
+ static iorap::expected<size_t /*bytes written*/, int /*errno*/> WriteFully(
+ const ::google::protobuf::MessageLite& message,
+ int fd,
+ std::string_view file_path = "<unknown>");
+
+ ProtobufIO() = delete;
+};
+
+} // namespace serialize
+} // namespace iorap
+
+#endif
+