From c103783d4bec8aa658e719c2ed7fe329d1d08676 Mon Sep 17 00:00:00 2001 From: Marcus Holland-Moritz Date: Tue, 26 Oct 2021 23:09:38 +0200 Subject: [PATCH] Section index support for speeding up mount times (fixes #48) --- include/dwarfs/fs_section.h | 2 + include/dwarfs/fstypes.h | 5 +- include/dwarfs/options.h | 1 + src/dwarfs/filesystem_v2.cpp | 125 +++++++++++++++++++++++++------ src/dwarfs/filesystem_writer.cpp | 34 +++++++++ src/dwarfs/fs_section.cpp | 70 +++++++++++++++++ src/dwarfs/fstypes.cpp | 1 + src/mkdwarfs.cpp | 6 +- test/dwarfs_compat.cpp | 22 +++++- 9 files changed, 240 insertions(+), 26 deletions(-) diff --git a/include/dwarfs/fs_section.h b/include/dwarfs/fs_section.h index 499d1cb7..0a283204 100644 --- a/include/dwarfs/fs_section.h +++ b/include/dwarfs/fs_section.h @@ -35,6 +35,8 @@ class mmif; class fs_section { public: fs_section(mmif& mm, size_t offset, int version); + fs_section(std::shared_ptr mm, section_type type, size_t offset, + size_t size, int version); size_t start() const { return impl_->start(); } size_t length() const { return impl_->length(); } diff --git a/include/dwarfs/fstypes.h b/include/dwarfs/fstypes.h index 616b4c7f..7910efbe 100644 --- a/include/dwarfs/fstypes.h +++ b/include/dwarfs/fstypes.h @@ -65,7 +65,7 @@ struct iovec_read_buf { }; constexpr uint8_t MAJOR_VERSION = 2; -constexpr uint8_t MINOR_VERSION = 3; +constexpr uint8_t MINOR_VERSION = 4; enum class section_type : uint16_t { BLOCK = 0, @@ -76,6 +76,9 @@ enum class section_type : uint16_t { METADATA_V2 = 8, // Frozen metadata. + + SECTION_INDEX = 9, + // Section index. }; struct file_header { diff --git a/include/dwarfs/options.h b/include/dwarfs/options.h index 8aea15e2..097be53b 100644 --- a/include/dwarfs/options.h +++ b/include/dwarfs/options.h @@ -57,6 +57,7 @@ struct filesystem_options { struct filesystem_writer_options { size_t max_queue_size{64 << 20}; bool remove_header{false}; + bool no_section_index{false}; }; struct inode_options { diff --git a/src/dwarfs/filesystem_v2.cpp b/src/dwarfs/filesystem_v2.cpp index 4121f8e2..8ffd4cf0 100644 --- a/src/dwarfs/filesystem_v2.cpp +++ b/src/dwarfs/filesystem_v2.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -54,6 +55,9 @@ namespace dwarfs { namespace { class filesystem_parser { + private: + static uint64_t constexpr section_offset_mask{(UINT64_C(1) << 48) - 1}; + public: static off_t find_image_offset(mmif& mm, off_t image_offset) { if (image_offset != filesystem_options::IMAGE_OFFSET_AUTO) { @@ -148,14 +152,31 @@ class filesystem_parser { major_ = fh->major; minor_ = fh->minor; + if (minor_ >= 4) { + find_index(); + } + rewind(); } std::optional next_section() { - if (offset_ < static_cast(mm_->size())) { - auto section = fs_section(*mm_, offset_, version_); - offset_ = section.end(); - return section; + if (index_.empty()) { + if (offset_ < static_cast(mm_->size())) { + auto section = fs_section(*mm_, offset_, version_); + offset_ = section.end(); + return section; + } + } else { + if (offset_ < static_cast(index_.size())) { + uint64_t id = index_[offset_++]; + uint64_t offset = id & section_offset_mask; + uint64_t next_offset = offset_ < static_cast(index_.size()) + ? index_[offset_] & section_offset_mask + : mm_->size() - image_offset_; + return fs_section(mm_, static_cast(id >> 48), + image_offset_ + offset, next_offset - offset, + version_); + } } return std::nullopt; @@ -169,7 +190,14 @@ class filesystem_parser { } void rewind() { - offset_ = image_offset_ + (version_ == 1 ? sizeof(file_header) : 0); + if (index_.empty()) { + offset_ = image_offset_; + if (version_ == 1) { + offset_ += sizeof(file_header); + } + } else { + offset_ = 0; + } } std::string version() const { @@ -180,13 +208,38 @@ class filesystem_parser { bool has_checksums() const { return version_ >= 2; } + bool has_index() const { return !index_.empty(); } + private: + void find_index() { + uint64_t index_pos; + + ::memcpy(&index_pos, mm_->as(mm_->size() - sizeof(uint64_t)), + sizeof(uint64_t)); + + if ((index_pos >> 48) == + static_cast(section_type::SECTION_INDEX)) { + index_pos &= section_offset_mask; + index_pos += image_offset_; + + if (index_pos < mm_->size()) { + auto section = fs_section(*mm_, index_pos, version_); + + if (section.check_fast(*mm_)) { + index_.resize(section.length() / sizeof(uint64_t)); + ::memcpy(index_.data(), section.data(*mm_).data(), section.length()); + } + } + } + } + std::shared_ptr mm_; off_t const image_offset_; off_t offset_{0}; int version_{0}; uint8_t major_{0}; uint8_t minor_{0}; + std::vector index_; }; using section_map = std::unordered_map; @@ -299,36 +352,68 @@ class filesystem_ final : public filesystem_v2::impl { void set_num_workers(size_t num) override { ir_.set_num_workers(num); } private: + filesystem_info const& get_info() const; + LOG_PROXY_DECL(LoggerPolicy); std::shared_ptr mm_; metadata_v2 meta_; inode_reader_v2 ir_; + mutable std::mutex mx_; + mutable filesystem_parser parser_; std::vector meta_buffer_; std::optional header_; - filesystem_info fsinfo_; + mutable std::unique_ptr fsinfo_; }; +template +filesystem_info const& filesystem_::get_info() const { + std::lock_guard lock(mx_); + + if (!fsinfo_) { + filesystem_info info; + + parser_.rewind(); + + while (auto s = parser_.next_section()) { + if (s->type() == section_type::BLOCK) { + ++info.block_count; + info.compressed_block_size += s->length(); + info.uncompressed_block_size += get_uncompressed_section_size(mm_, *s); + } else if (s->type() == section_type::METADATA_V2) { + info.compressed_metadata_size += s->length(); + info.uncompressed_metadata_size += + get_uncompressed_section_size(mm_, *s); + } + } + + fsinfo_ = std::make_unique(info); + } + + return *fsinfo_; +} + template filesystem_::filesystem_(logger& lgr, std::shared_ptr mm, const filesystem_options& options, int inode_offset) : LOG_PROXY_INIT(lgr) - , mm_(std::move(mm)) { - filesystem_parser parser(mm_, options.image_offset); + , mm_(std::move(mm)) + , parser_(mm_, options.image_offset) { block_cache cache(lgr, mm_, options.block_cache); - header_ = parser.header(); + if (parser_.has_index()) { + LOG_DEBUG << "found valid section index"; + } + + header_ = parser_.header(); section_map sections; - while (auto s = parser.next_section()) { - LOG_DEBUG << "section " << s->description() << " @ " << s->start() << " [" + while (auto s = parser_.next_section()) { + LOG_DEBUG << "section " << s->name() << " @ " << s->start() << " [" << s->length() << " bytes]"; if (s->type() == section_type::BLOCK) { cache.insert(*s); - ++fsinfo_.block_count; - fsinfo_.compressed_block_size += s->length(); - fsinfo_.uncompressed_block_size += get_uncompressed_section_size(mm_, *s); } else { if (!s->check_fast(*mm_)) { DWARFS_THROW(runtime_error, "checksum error in section: " + s->name()); @@ -337,12 +422,6 @@ filesystem_::filesystem_(logger& lgr, std::shared_ptr mm, if (!sections.emplace(s->type(), *s).second) { DWARFS_THROW(runtime_error, "duplicate section: " + s->name()); } - - if (s->type() == section_type::METADATA_V2) { - fsinfo_.compressed_metadata_size += s->length(); - fsinfo_.uncompressed_metadata_size += - get_uncompressed_section_size(mm_, *s); - } } } @@ -350,7 +429,7 @@ filesystem_::filesystem_(logger& lgr, std::shared_ptr mm, meta_ = make_metadata(lgr, mm_, sections, schema_buffer, meta_buffer_, options.metadata, inode_offset, false, - options.lock_mode, !parser.has_checksums()); + options.lock_mode, !parser_.has_checksums()); LOG_DEBUG << "read " << cache.block_count() << " blocks and " << meta_.size() << " bytes of metadata"; @@ -362,7 +441,7 @@ filesystem_::filesystem_(logger& lgr, std::shared_ptr mm, template void filesystem_::dump(std::ostream& os, int detail_level) const { - meta_.dump(os, detail_level, fsinfo_, + meta_.dump(os, detail_level, get_info(), [&](const std::string& indent, uint32_t inode) { if (auto chunks = meta_.get_chunks(inode)) { os << indent << chunks->size() << " chunks in inode " << inode @@ -539,7 +618,7 @@ void filesystem_v2::rewrite(logger& lgr, progress& prog, prog.filesystem_size += s->length(); if (s->type() == section_type::BLOCK) { ++prog.block_count; - } else { + } else if (s->type() != section_type::SECTION_INDEX) { if (!sections.emplace(s->type(), *s).second) { DWARFS_THROW(runtime_error, "duplicate section: " + s->name()); } diff --git a/src/dwarfs/filesystem_writer.cpp b/src/dwarfs/filesystem_writer.cpp index 58fb2c37..3ea034cc 100644 --- a/src/dwarfs/filesystem_writer.cpp +++ b/src/dwarfs/filesystem_writer.cpp @@ -255,6 +255,8 @@ class filesystem_writer_ final : public filesystem_writer::impl { void write(const T& obj); void write(folly::ByteRange range); void writer_thread(); + void push_section_index(section_type type); + void write_section_index(); size_t mem_used() const; std::ostream& os_; @@ -272,6 +274,8 @@ class filesystem_writer_ final : public filesystem_writer::impl { volatile bool flush_; std::thread writer_thread_; uint32_t section_number_{0}; + std::vector section_index_; + std::ostream::pos_type header_size_{0}; }; template @@ -296,6 +300,7 @@ filesystem_writer_::filesystem_writer_( LOG_WARN << "header will not be written because remove_header is set"; } else { os_ << header_->rdbuf(); + header_size_ = os_.tellp(); } } } @@ -378,6 +383,8 @@ void filesystem_writer_::write(folly::ByteRange range) { template void filesystem_writer_::write(fsblock const& fsb) { + push_section_index(fsb.type()); + write(fsb.header()); write(fsb.data()); @@ -434,6 +441,7 @@ void filesystem_writer_::copy_header(folly::ByteRange header) { LOG_WARN << "replacing old header"; } else { write(header); + header_size_ = os_.tellp(); } } } @@ -471,6 +479,32 @@ void filesystem_writer_::flush() { cond_.notify_one(); writer_thread_.join(); + + if (!options_.no_section_index) { + write_section_index(); + } +} + +template +void filesystem_writer_::push_section_index(section_type type) { + section_index_.push_back((static_cast(type) << 48) | + static_cast(os_.tellp() - header_size_)); +} + +template +void filesystem_writer_::write_section_index() { + push_section_index(section_type::SECTION_INDEX); + auto data = + folly::ByteRange(reinterpret_cast(section_index_.data()), + sizeof(section_index_[0]) * section_index_.size()); + + auto fsb = fsblock(section_type::SECTION_INDEX, compression_type::NONE, data, + section_number_++); + + fsb.compress(wg_); + fsb.wait_until_compressed(); + + write(fsb); } } // namespace diff --git a/src/dwarfs/fs_section.cpp b/src/dwarfs/fs_section.cpp index 9098de5f..13258ca5 100644 --- a/src/dwarfs/fs_section.cpp +++ b/src/dwarfs/fs_section.cpp @@ -20,6 +20,7 @@ */ #include +#include #include @@ -143,6 +144,41 @@ class fs_section_v2 : public fs_section::impl { section_header_v2 hdr_; }; +class fs_section_v2_lazy : public fs_section::impl { + public: + fs_section_v2_lazy(std::shared_ptr mm, section_type type, size_t offset, + size_t size); + + size_t start() const override { return offset_ + sizeof(section_header_v2); } + size_t length() const override { return size_ - sizeof(section_header_v2); } + + compression_type compression() const override { + return section().compression(); + } + + section_type type() const override { return type_; } + + std::string name() const override { return get_section_name(type_); } + + std::string description() const override { return section().description(); } + + bool check_fast(mmif& mm) const override { return section().check_fast(mm); } + + bool verify(mmif& mm) const override { return section().verify(mm); } + + folly::ByteRange data(mmif& mm) const override { return section().data(mm); } + + private: + fs_section::impl const& section() const; + + std::mutex mutable mx_; + std::unique_ptr mutable sec_; + std::shared_ptr mutable mm_; + section_type type_; + size_t offset_; + size_t size_; +}; + fs_section::fs_section(mmif& mm, size_t offset, int version) { switch (version) { case 1: @@ -160,6 +196,21 @@ fs_section::fs_section(mmif& mm, size_t offset, int version) { } } +fs_section::fs_section(std::shared_ptr mm, section_type type, + size_t offset, size_t size, int version) { + switch (version) { + case 2: + impl_ = + std::make_shared(std::move(mm), type, offset, size); + break; + + default: + DWARFS_THROW(runtime_error, + fmt::format("unsupported section version {} [lazy]", version)); + break; + } +} + fs_section_v1::fs_section_v1(mmif& mm, size_t offset) { read_section_header_common(hdr_, start_, mm, offset); check_section(*this); @@ -170,4 +221,23 @@ fs_section_v2::fs_section_v2(mmif& mm, size_t offset) { check_section(*this); } +fs_section_v2_lazy::fs_section_v2_lazy(std::shared_ptr mm, + section_type type, size_t offset, + size_t size) + : mm_{std::move(mm)} + , type_{type} + , offset_{offset} + , size_{size} {} + +fs_section::impl const& fs_section_v2_lazy::section() const { + std::lock_guard lock(mx_); + + if (!sec_) { + sec_ = std::make_unique(*mm_, offset_); + mm_.reset(); + } + + return *sec_; +} + } // namespace dwarfs diff --git a/src/dwarfs/fstypes.cpp b/src/dwarfs/fstypes.cpp index 835dbd42..93cade0e 100644 --- a/src/dwarfs/fstypes.cpp +++ b/src/dwarfs/fstypes.cpp @@ -39,6 +39,7 @@ const std::map sections{ SECTION_TYPE_(BLOCK), SECTION_TYPE_(METADATA_V2_SCHEMA), SECTION_TYPE_(METADATA_V2), + SECTION_TYPE_(SECTION_INDEX), #undef SECTION_TYPE_ }; diff --git a/src/mkdwarfs.cpp b/src/mkdwarfs.cpp index 6044b5d5..49d4206c 100644 --- a/src/mkdwarfs.cpp +++ b/src/mkdwarfs.cpp @@ -334,7 +334,7 @@ int mkdwarfs(int argc, char** argv) { schema_compression, metadata_compression, log_level_str, timestamp, time_resolution, order, progress_mode, recompress_opts, pack_metadata; size_t num_workers; - bool no_progress = false, remove_header = false; + bool no_progress = false, remove_header = false, no_section_index = false; unsigned level; uint16_t uid, gid; @@ -439,6 +439,9 @@ int mkdwarfs(int argc, char** argv) { po::value(&remove_header)->zero_tokens(), "remove any header present before filesystem data" " (use with --recompress)") + ("no-section-index", + po::value(&no_section_index)->zero_tokens(), + "don't add section index to file system") ("log-level", po::value(&log_level_str)->default_value("info"), "log level (error, warn, info, debug, trace)") @@ -803,6 +806,7 @@ int mkdwarfs(int argc, char** argv) { filesystem_writer_options fswopts; fswopts.max_queue_size = mem_limit; fswopts.remove_header = remove_header; + fswopts.no_section_index = no_section_index; std::unique_ptr header_ifs; diff --git a/test/dwarfs_compat.cpp b/test/dwarfs_compat.cpp index 17739b97..523edc4e 100644 --- a/test/dwarfs_compat.cpp +++ b/test/dwarfs_compat.cpp @@ -1202,7 +1202,27 @@ TEST_P(rewrite, filesystem_rewrite) { auto mm = std::make_shared(rewritten4.str()); EXPECT_NO_THROW(filesystem_v2::identify(lgr, mm, idss)); EXPECT_FALSE(filesystem_v2::header(mm)) - << folly::hexDump(rewritten3.str().data(), rewritten3.str().size()); + << folly::hexDump(rewritten4.str().data(), rewritten4.str().size()); + filesystem_v2 fs(lgr, mm); + check_dynamic(version, fs); + } + + std::ostringstream rewritten5; + + { + filesystem_writer_options fsw_opts; + fsw_opts.no_section_index = true; + filesystem_writer fsw(rewritten5, lgr, wg, prog, bc, fsw_opts); + filesystem_v2::rewrite(lgr, prog, + std::make_shared(rewritten4.str()), + fsw, opts); + } + + { + auto mm = std::make_shared(rewritten5.str()); + EXPECT_NO_THROW(filesystem_v2::identify(lgr, mm, idss)); + EXPECT_FALSE(filesystem_v2::header(mm)) + << folly::hexDump(rewritten5.str().data(), rewritten5.str().size()); filesystem_v2 fs(lgr, mm); check_dynamic(version, fs); }