depot_query: add directory cache

By caching directory listings, the performance of depot_query is no
longer impeded by a massive amount of stat calls while searching for ROM
module locations.

Issue #4611
This commit is contained in:
Norman Feske 2022-09-14 15:51:52 +02:00 committed by Christian Helmuth
parent b4441bff04
commit 851ae574d1

View File

@ -17,6 +17,7 @@
#include <base/attached_rom_dataspace.h> #include <base/attached_rom_dataspace.h>
#include <os/reporter.h> #include <os/reporter.h>
#include <os/vfs.h> #include <os/vfs.h>
#include <util/dictionary.h>
#include <depot/archive.h> #include <depot/archive.h>
/* fs_query includes */ /* fs_query includes */
@ -28,12 +29,93 @@ namespace Depot_query {
typedef String<64> Rom_label; typedef String<64> Rom_label;
struct Directory_cache;
struct Recursion_limit; struct Recursion_limit;
struct Dependencies; struct Dependencies;
struct Main; struct Main;
} }
struct Depot_query::Directory_cache : Noncopyable
{
Allocator &_alloc;
using Name = Directory::Entry::Name;
struct Listing;
using Listings = Dictionary<Listing, Directory::Path>;
struct Listing : Listings::Element
{
Allocator &_alloc;
struct File;
using Files = Dictionary<File, Name>;
struct File : Files::Element
{
File(Files &files, Name const &name) : Files::Element(files, name) { }
};
Files _files { };
Listing(Listings &listings, Allocator &alloc, Directory &dir,
Directory::Path const &path)
:
Listings::Element(listings, path), _alloc(alloc)
{
try {
Directory(dir, path).for_each_entry([&] (Directory::Entry const &entry) {
new (_alloc) File(_files, entry.name()); });
}
catch (Directory::Nonexistent_directory) {
warning("directory '", path, "' does not exist");
}
}
~Listing()
{
auto destroy_fn = [&] (File &f) { destroy(_alloc, &f); };
while (_files.with_any_element(destroy_fn));
}
bool file_exists(Name const &name) const { return _files.exists(name); }
};
Listings mutable _listings { };
Directory_cache(Allocator &alloc) : _alloc(alloc) { }
~Directory_cache()
{
auto destroy_fn = [&] (Listing &l) { destroy(_alloc, &l); };
while (_listings.with_any_element(destroy_fn));
}
bool file_exists(Directory &dir, Directory::Path const &path, Name const &name) const
{
bool listing_known = false;
bool const result =
_listings.with_element(path,
[&] /* match */ (Listing const &listing) {
listing_known = true;
return listing.file_exists(name);
},
[&] /* no_match */ { return false; });
if (listing_known)
return result;
Listing &new_listing = *new (_alloc) Listing(_listings, _alloc, dir, path);
return new_listing.file_exists(name);
}
};
class Depot_query::Recursion_limit : Noncopyable class Depot_query::Recursion_limit : Noncopyable
{ {
public: public:
@ -165,6 +247,8 @@ struct Depot_query::Main
Directory _depot_dir { _root, "depot" }; Directory _depot_dir { _root, "depot" };
Constructible<Directory_cache> _directory_cache { };
Signal_handler<Main> _config_handler { Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config }; _env.ep(), *this, &Main::_handle_config };
@ -194,9 +278,14 @@ struct Depot_query::Main
Architecture _architecture { }; Architecture _architecture { };
bool _file_exists(Directory::Path const &path) bool _file_exists(Directory::Path const &path, Rom_label const &file_name)
{ {
return _depot_dir.file_exists(path); if (!_directory_cache.constructed()) {
error("directory cache is unexpectedly not constructed");
return false;
}
return _directory_cache->file_exists(_depot_dir, path, file_name);
} }
template <typename FN> template <typename FN>
@ -253,6 +342,8 @@ struct Depot_query::Main
Xml_node const config = _config.xml(); Xml_node const config = _config.xml();
_directory_cache.construct(_heap);
/* /*
* Depending of the 'query' config attribute, we obtain the query * Depending of the 'query' config attribute, we obtain the query
* information from a separate ROM session (attribute value "rom") * information from a separate ROM session (attribute value "rom")
@ -394,25 +485,25 @@ Depot_query::Main::_find_rom_in_pkg(Directory::Path const &pkg_path,
case Archive::SRC: case Archive::SRC:
{ {
Archive::Path const Archive::Path const
rom_path(Archive::user(archive_path), "/bin/", rom_path(Archive::user(archive_path), "/bin/",
_architecture, "/", _architecture, "/",
Archive::name(archive_path), "/", Archive::name(archive_path), "/",
Archive::version(archive_path), "/", rom_label); Archive::version(archive_path));
if (_file_exists(rom_path)) if (_file_exists(rom_path, rom_label))
result = rom_path; result = Archive::Path(rom_path, "/", rom_label);
} }
break; break;
case Archive::RAW: case Archive::RAW:
{ {
Archive::Path const Archive::Path const
rom_path(Archive::user(archive_path), "/raw/", rom_path(Archive::user(archive_path), "/raw/",
Archive::name(archive_path), "/", Archive::name(archive_path), "/",
Archive::version(archive_path), "/", rom_label); Archive::version(archive_path));
if (_file_exists(rom_path)) if (_file_exists(rom_path, rom_label))
result = rom_path; result = Archive::Path(rom_path, "/", rom_label);
} }
break; break;