// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 << ""; } return os; } static constexpr PrefetchStrategy kPrefetchStrategy = PrefetchStrategy::kFadvise; static uint64_t kMaxPrefetchBytes = ::android::base::GetUintProperty( "iorapd.max_prefetch_bytes", /*default*/100 * 1024 * 1024); // 100MB by default 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; 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 recents_; std::mutex mutex_; void RecordRecent(TaskId task_id, size_t file_lengths_sum) { std::lock_guard 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 session_manager_; SessionKind session_kind_; std::unordered_map 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, 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 = 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 = 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 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(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 prefetch_bytes = 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); } else { prefetch_bytes += static_cast(file_entry.file_length()); if (prefetch_bytes >= kMaxPrefetchBytes) { LOG(WARNING) << "The prefetching size is " << prefetch_bytes << " and it exceeds the threshold " << kMaxPrefetchBytes; break; } } length_sum += static_cast(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(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 ReadAhead::PrefetchSizeInBytes(const std::string& file_path) { serialize::ArenaPtr 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(file_entry.file_length()); } return length_sum; } } // namespace prefetcher } // namespace iorap