blob: dee14309c76a9bdd720cd2b393404f7c0e996023 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "arc/setup/arc_read_ahead.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <algorithm>
#include <array>
#include <map>
#include <string>
#include <unordered_map>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/scoped_file.h>
#include <base/time/time.h>
#include <base/timer/elapsed_timer.h>
// TODO(yusukes): Include a different header for P.
#include "arc/setup/arc_read_ahead_files_nyc.h"
namespace arc {
namespace {
// A map from a file name (base name without a path) to its occurence in the
// file tree. This map is just for checking if |kImportantFiles| is up to date.
using FileNameToCountMap = std::unordered_map<std::string, size_t>;
// A multimap from a read-ahead size to a file name (full path). Use the size as
// a key to sort files by size.
using FilesToReadMap = std::multimap<int64_t, base::FilePath>;
// Checks if |base_name| should be read-ahead, and returns >0 when it is. The
// number returned should be passed as the 3rd argument of readahead(2). Returns
// 0 when |base_name| should not be read-ahead. This function also updates
// |usage| when |base_name| is in |kImportantFiles|,
int64_t GetReadAheadSize(const base::FilePath& base_name,
int64_t size,
FileNameToCountMap* usage) {
auto it = usage->find(base_name.value());
if (it != usage->end()) {
++(it->second);
return size;
}
const std::string extension = base_name.Extension();
if (std::find(kImportantExtensions.begin(), kImportantExtensions.end(),
extension) != kImportantExtensions.end()) {
return std::min(arc::kDefaultReadAheadSize, size);
}
return 0; // no read ahead.
}
FilesToReadMap GetFileList(const base::FilePath& scan_root) {
FilesToReadMap result;
FileNameToCountMap usage;
for (auto* file_name : kImportantFiles)
usage.emplace(file_name, 0);
// Scan all files in |scan_root|.
size_t num_files = 0;
base::FileEnumerator enumerator(scan_root, true /* recursive */,
base::FileEnumerator::FILES);
for (base::FilePath name = enumerator.Next(); !name.empty();
name = enumerator.Next()) {
++num_files;
const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
if ((info.stat().st_mode & S_IFMT) != S_IFREG)
continue; // do not handle device files, symlinks, etc.
const int64_t read_ahead_bytes =
GetReadAheadSize(name.BaseName(), info.GetSize(), &usage);
if (read_ahead_bytes > 0)
result.emplace(read_ahead_bytes, name);
}
// Check if |scan_root| has all files in |kImportantFiles|. Print LOGs if it
// doesn't.
for (auto it = usage.begin(); it != usage.end(); ++it) {
if (it->second > 0)
continue;
LOG(WARNING) << it->first
<< " is in |kImportantFiles|, but is not found in "
<< scan_root.value()
<< ". Update the table for better performance.";
}
LOG(INFO) << num_files << " files checked, found " << result.size()
<< " files to read";
return result;
}
} // namespace
std::pair<size_t, size_t> EmulateArcUreadahead(const base::FilePath& scan_root,
const base::TimeDelta& timeout) {
base::ElapsedTimer timer;
size_t num_files_read = 0;
size_t num_bytes_read = 0;
FilesToReadMap files_to_read(GetFileList(scan_root));
// Use rbegin/rend to read larger files first.
for (auto it = files_to_read.rbegin(); it != files_to_read.rend(); ++it) {
const int64_t read_ahead_bytes = it->first;
const base::FilePath& name = it->second;
if (timeout <= timer.Elapsed()) {
LOG(WARNING) << "Timed out after reading " << num_files_read << " files";
break;
}
base::ScopedFD scoped_fd(open(name.value().c_str(), O_RDONLY));
if (!scoped_fd.is_valid()) {
PLOG(WARNING) << "open failed for " << name.value();
continue;
}
if (readahead(scoped_fd.get(), 0 /* offset */, read_ahead_bytes)) {
PLOG(WARNING) << "readahead failed for " << name.value();
continue;
}
++num_files_read;
num_bytes_read += read_ahead_bytes;
}
LOG(INFO) << "Read " << num_files_read << " files and " << num_bytes_read
<< " bytes in " << timer.Elapsed().InMillisecondsRoundedUp()
<< " ms";
return std::make_pair(num_files_read, num_bytes_read);
}
} // namespace arc