461 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "base/progress_monitor.h"
 | |
| #include "persistent-data/file_utils.h"
 | |
| #include "block-cache/copier.h"
 | |
| #include "caching/commands.h"
 | |
| #include "caching/mapping_array.h"
 | |
| #include "caching/metadata.h"
 | |
| #include "version.h"
 | |
| 
 | |
| #include <boost/optional.hpp>
 | |
| #include <getopt.h>
 | |
| #include <string>
 | |
| #include <stdexcept>
 | |
| #include <boost/optional/optional_io.hpp>
 | |
| 
 | |
| using namespace bcache;
 | |
| using namespace caching;
 | |
| using namespace boost;
 | |
| using namespace std;
 | |
| 
 | |
| //----------------------------------------------------------------
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| 	template <typename T> T safe_div(T const n, T const d, T const def) {
 | |
| 		return (d == T()) ? def : (n / d);
 | |
| 	}
 | |
| 
 | |
| 	//--------------------------------
 | |
| 
 | |
| 	struct flags {
 | |
| 		flags()
 | |
| 			: cache_size(4 * 1024 * 1024),
 | |
| 			  sort_buffers(16 * 1024),
 | |
| 			  list_failed_blocks(false),
 | |
| 			  update_metadata(true) {
 | |
| 		}
 | |
| 
 | |
| 		// The sort buffers have a dramatic effect on the
 | |
| 		// performance.  We give up 10% of the general buffer space
 | |
| 		// for them.
 | |
| 		void calc_sort_buffer_size() {
 | |
| 			size_t sbs = cache_size / 10;
 | |
| 			cache_size = cache_size - sbs;
 | |
| 
 | |
| 			sort_buffers = sbs / sizeof(copy_op);
 | |
| 		}
 | |
| 
 | |
| 		using maybe_string = boost::optional<string>;
 | |
| 
 | |
| 		size_t cache_size;
 | |
| 		unsigned sort_buffers;
 | |
| 		maybe_string metadata_dev;
 | |
| 		maybe_string origin_dev;
 | |
| 		maybe_string fast_dev;
 | |
| 		bool list_failed_blocks;
 | |
| 		bool update_metadata;
 | |
| 	};
 | |
| 
 | |
| 	//--------------------------------
 | |
| 
 | |
| 	class copy_batch {
 | |
| 	public:
 | |
| 		copy_batch(unsigned nr)
 | |
| 		: max_(nr),
 | |
| 		  count_(0),
 | |
| 		  ops_(nr) {
 | |
| 		}
 | |
| 
 | |
| 		bool space() const {
 | |
| 			return count_ < max_;
 | |
| 		}
 | |
| 
 | |
| 		void push_op(copy_op const &op) {
 | |
| 			if (!space())
 | |
| 				throw runtime_error("copy_batch out of space");
 | |
| 
 | |
| 			ops_[count_++] = op;
 | |
| 		}
 | |
| 
 | |
| 		void reset() {
 | |
| 			count_ = 0;
 | |
| 		}
 | |
| 
 | |
| 		vector<copy_op>::iterator begin() {
 | |
| 			return ops_.begin();
 | |
| 		}
 | |
| 
 | |
| 		vector<copy_op>::iterator end() {
 | |
| 			return ops_.begin() + count_;
 | |
| 		}
 | |
| 
 | |
| 	private:
 | |
| 		unsigned max_;
 | |
| 		unsigned count_;
 | |
| 		vector<copy_op> ops_;
 | |
| 	};
 | |
| 
 | |
| 	class copy_visitor : public mapping_visitor {
 | |
| 	public:
 | |
| 		copy_visitor(copier &c, unsigned sort_buffer, bool only_dirty,
 | |
| 			     bool list_failed_blocks,
 | |
| 			     progress_monitor &monitor, unsigned cache_blocks)
 | |
| 			: copier_(c),
 | |
| 			  block_size_(c.get_block_size()),
 | |
| 			  only_dirty_(only_dirty),
 | |
| 			  list_failed_blocks_(list_failed_blocks),
 | |
| 			  batch_(sort_buffer),
 | |
| 			  monitor_(monitor),
 | |
| 			  cache_blocks_(cache_blocks) {
 | |
| 		}
 | |
| 
 | |
| 		virtual void visit(block_address cblock, mapping const &m) {
 | |
| 			stats_.blocks_scanned = cblock;
 | |
| 			update_monitor();
 | |
| 
 | |
| 			if (!(m.flags_ & M_VALID))
 | |
| 				return;
 | |
| 
 | |
| 			if (only_dirty_ && !(m.flags_ & M_DIRTY))
 | |
| 				return;
 | |
| 
 | |
| 			copy_op cop;
 | |
| 			cop.src_b = cblock;
 | |
| 			cop.src_e = cblock + 1ull;
 | |
| 			cop.dest_b = m.oblock_;
 | |
| 
 | |
| 			// blocks
 | |
| 			stats_.blocks_needed++;
 | |
| 			batch_.push_op(cop);
 | |
| 			if (!batch_.space())
 | |
| 				issue();
 | |
| 		}
 | |
| 
 | |
| 		void issue() {
 | |
| 			auto compare_dest = [](copy_op const &lhs, copy_op const &rhs) {
 | |
| 				return lhs.dest_b < rhs.dest_b;
 | |
| 			};
 | |
| 			sort(batch_.begin(), batch_.end(), compare_dest);
 | |
| 
 | |
| 			auto e = batch_.end();
 | |
| 			for (auto it = batch_.begin(); it != e; ++it) {
 | |
| 				copier_.issue(*it);
 | |
| 				stats_.blocks_issued++;
 | |
| 				update_monitor();
 | |
| 
 | |
| 				check_for_completed_copies();
 | |
| 			}
 | |
| 			check_for_completed_copies();
 | |
| 
 | |
| 			batch_.reset();
 | |
| 		}
 | |
| 
 | |
| 		void check_for_completed_copies(bool block = false) {
 | |
| 			optional<copy_op> mop;
 | |
| 
 | |
| 			do {
 | |
| 				if (block)
 | |
| 					mop = copier_.wait();
 | |
| 
 | |
| 				else {
 | |
| 					unsigned micro = 0;
 | |
| 					mop = copier_.wait(micro);
 | |
| 				}
 | |
| 
 | |
| 				if (mop) {
 | |
| 					inc_completed(*mop);
 | |
| 					if (!mop->success()) {
 | |
| 						failed_blocks_.insert(*mop);
 | |
| 						failed_cblocks_.insert(mop->src_b);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 			} while (mop);
 | |
| 		}
 | |
| 
 | |
| 		void complete() {
 | |
| 			issue();
 | |
| 
 | |
| 			while (copier_.nr_pending())
 | |
| 				check_for_completed_copies(true);
 | |
| 
 | |
| 			monitor_.update_percent(100);
 | |
| 			cerr << "\n";
 | |
| 		}
 | |
| 
 | |
| 		void inc_completed(copy_op const &op) {
 | |
| 			stats_.blocks_completed++;
 | |
| 			update_monitor();
 | |
| 		}
 | |
| 
 | |
| 		void update_monitor() {
 | |
| 			static unsigned call_count = 0;
 | |
| 			if (call_count++ % 128)
 | |
| 				return;
 | |
| 
 | |
| 			uint64_t scanned = stats_.blocks_scanned * 100 / cache_blocks_;
 | |
| 			uint64_t copied = safe_div<block_address>(stats_.blocks_completed * 100,
 | |
| 								  stats_.blocks_needed, 100ull);
 | |
| 			uint64_t percent = min<uint64_t>(scanned, copied);
 | |
| 			monitor_.update_percent(percent);
 | |
| 		}
 | |
| 
 | |
| 		struct copy_stats {
 | |
| 			copy_stats()
 | |
| 				: blocks_scanned(0),
 | |
| 				  blocks_needed(0),
 | |
| 				  blocks_issued(0),
 | |
| 				  blocks_completed(0),
 | |
| 				  blocks_failed(0) {
 | |
| 			}
 | |
| 
 | |
| 			block_address blocks_scanned;
 | |
| 			block_address blocks_needed;
 | |
| 			block_address blocks_issued;
 | |
| 			block_address blocks_completed;
 | |
| 			block_address blocks_failed;
 | |
| 		};
 | |
| 
 | |
| 		copy_stats const &get_stats() const {
 | |
| 			return stats_;
 | |
| 		}
 | |
| 
 | |
| 		set<block_address> failed_writebacks() const {
 | |
| 			return failed_cblocks_;
 | |
| 		}
 | |
| 
 | |
| 	private:
 | |
| 		copier &copier_;
 | |
| 		unsigned block_size_;
 | |
| 		bool only_dirty_;
 | |
| 		bool list_failed_blocks_;
 | |
| 
 | |
| 		copy_stats stats_;
 | |
| 		copy_batch batch_;
 | |
| 		progress_monitor &monitor_;
 | |
| 		unsigned cache_blocks_;
 | |
| 
 | |
| 		set<copy_op> failed_blocks_;
 | |
| 		set<block_address> failed_cblocks_;
 | |
| 	};
 | |
| 
 | |
| 	//--------------------------------
 | |
| 
 | |
| 	using namespace mapping_array_damage;
 | |
| 
 | |
| 	class ignore_damage_visitor : public damage_visitor {
 | |
| 	public:
 | |
| 		ignore_damage_visitor()
 | |
| 			: corruption_(false) {
 | |
| 		}
 | |
| 
 | |
| 		void visit(missing_mappings const &d) {
 | |
| 			cerr << "missing mappings (" << d.keys_.begin_ << ", " << d.keys_.end_ << "]\n";
 | |
| 			corruption_ = true;
 | |
| 		}
 | |
| 
 | |
| 		void visit(invalid_mapping const &d) {
 | |
| 			cerr << "invalid mapping cblock = " << d.cblock_ << ", oblock = " << d.m_.oblock_ << "\n";
 | |
| 			corruption_ = true;
 | |
| 		}
 | |
| 
 | |
| 		bool was_corruption() const {
 | |
| 			return corruption_;
 | |
| 		}
 | |
| 
 | |
| 	private:
 | |
| 		bool corruption_;
 | |
| 	};
 | |
| 
 | |
| 	bool clean_shutdown(metadata const &md) {
 | |
| 		return md.sb_.flags.get_flag(superblock_flags::CLEAN_SHUTDOWN);
 | |
| 	}
 | |
| 
 | |
| 	void update_metadata(metadata &md, set<block_address> const &failed_writebacks) {
 | |
| 		cout << "Updating metadata ... ";
 | |
| 
 | |
| 		cout.flush();
 | |
| 
 | |
| 		auto &mappings = md.mappings_;
 | |
| 		for (block_address cblock = 0; cblock < mappings->get_nr_entries(); cblock++) {
 | |
| 			auto m = mappings->get(cblock);
 | |
| 			if (!(m.flags_ & M_VALID))
 | |
| 				continue;
 | |
| 
 | |
| 			if (!(m.flags_ & M_DIRTY))
 | |
| 				continue;
 | |
| 
 | |
| 			if (failed_writebacks.count(cblock))
 | |
| 				continue;
 | |
| 
 | |
| 			m.flags_ &= ~M_DIRTY;
 | |
| 			cerr << "clearing dirty flag for block " << cblock << "\n";
 | |
| 			mappings->set(cblock, m);
 | |
| 		}
 | |
| 		md.commit(true);
 | |
| 		cout << "done\n";
 | |
| 		cout.flush();
 | |
| 	}
 | |
| 
 | |
| 	int writeback_(flags const &f) {
 | |
| 		block_manager<>::ptr bm = open_bm(*f.metadata_dev, block_manager<>::READ_WRITE);
 | |
| 		metadata md(bm);
 | |
| 
 | |
| 		// FIXME: we're going to have to copy runs to get the through put with small block sizes
 | |
| 		unsigned max_ios = f.cache_size / (md.sb_.data_block_size << SECTOR_SHIFT);
 | |
| 		aio_engine engine(max_ios);
 | |
| 		copier c(engine, *f.fast_dev, *f.origin_dev,
 | |
| 			 md.sb_.data_block_size, f.cache_size);
 | |
| 
 | |
| 		auto bar = create_progress_bar("Copying data");
 | |
| 		copy_visitor cv(c, f.sort_buffers, clean_shutdown(md), f.list_failed_blocks,
 | |
| 				*bar, md.sb_.cache_blocks);
 | |
| 
 | |
| 		ignore_damage_visitor dv;
 | |
| 
 | |
| 		walk_mapping_array(*md.mappings_, cv, dv);
 | |
| 		cv.complete();
 | |
| 
 | |
| 		auto stats = cv.get_stats();
 | |
| 		cout << stats.blocks_issued - stats.blocks_failed << "/"
 | |
| 		     << stats.blocks_issued << " blocks successfully copied.\n";
 | |
| 
 | |
| 		if (stats.blocks_failed)
 | |
| 			cout << stats.blocks_failed << " blocks were not copied\n";
 | |
| 
 | |
| 		if (dv.was_corruption()) {
 | |
| 			cout << "Metadata corruption was found, some data may not have been copied.\n";
 | |
| 			if (f.update_metadata)
 | |
| 				cout << "Unable to update metadata.\n";
 | |
| 
 | |
| 		} else if (f.update_metadata)
 | |
| 			update_metadata(md, cv.failed_writebacks());
 | |
| 
 | |
| 		return (stats.blocks_failed || dv.was_corruption()) ? 1 : 0;
 | |
| 	}
 | |
| 
 | |
| 	int writeback(flags const &f) {
 | |
| 		int r;
 | |
| 
 | |
| 		try {
 | |
| 			r = writeback_(f);
 | |
| 
 | |
| 		} catch (std::exception &e) {
 | |
| 			cerr << e.what() << endl;
 | |
| 			return 1;
 | |
| 		}
 | |
| 
 | |
| 		return r;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------
 | |
| 
 | |
| cache_writeback_cmd::cache_writeback_cmd()
 | |
| 	: command("cache_writeback")
 | |
| {
 | |
| }
 | |
| 
 | |
| void
 | |
| cache_writeback_cmd::usage(std::ostream &out) const
 | |
| {
 | |
| 	out << "Usage: " << get_name() << " [options]\n"
 | |
| 	    << "\t\t--metadata-device <dev>\n"
 | |
| 	    << "\t\t--origin-device <dev>\n"
 | |
| 	    << "\t\t--fast-device <dev>\n"
 | |
| 	    << "\t\t--buffer-size-meg <size>\n"
 | |
| 	    << "\t\t--list-failed-blocks\n"
 | |
| 	    << "\t\t--no-metadata-update\n"
 | |
| 	    << "Options:\n"
 | |
| 	    << "  {-h|--help}\n"
 | |
| 	    << "  {-V|--version}" << endl;
 | |
| }
 | |
| 
 | |
| int
 | |
| cache_writeback_cmd::run(int argc, char **argv)
 | |
| {
 | |
| 	int c;
 | |
| 	flags fs;
 | |
| 	char const *short_opts = "hV";
 | |
| 	option const long_opts[] = {
 | |
| 		{ "metadata-device", required_argument, NULL, 0 },
 | |
| 		{ "origin-device", required_argument, NULL, 1 },
 | |
| 		{ "fast-device", required_argument, NULL, 2 },
 | |
| 		{ "buffer-size-meg", required_argument, NULL, 3 },
 | |
| 		{ "list-failed-blocks", no_argument, NULL, 4 },
 | |
| 		{ "no-metadata-update", no_argument, NULL, 5 },
 | |
| 		{ "help", no_argument, NULL, 'h'},
 | |
| 		{ "version", no_argument, NULL, 'V'},
 | |
| 		{ NULL, no_argument, NULL, 0 }
 | |
| 	};
 | |
| 
 | |
| 	while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
 | |
| 		switch(c) {
 | |
| 		case 0:
 | |
| 			fs.metadata_dev = optarg;
 | |
| 			break;
 | |
| 
 | |
| 		case 1:
 | |
| 			fs.origin_dev = optarg;
 | |
| 			break;
 | |
| 
 | |
| 		case 2:
 | |
| 			fs.fast_dev = optarg;
 | |
| 			break;
 | |
| 
 | |
| 		case 3:
 | |
| 			fs.cache_size = parse_uint64(optarg, "buffer size") * 1024 * 1024;
 | |
| 			break;
 | |
| 
 | |
| 		case 4:
 | |
| 			fs.list_failed_blocks = true;
 | |
| 			break;
 | |
| 
 | |
| 		case 5:
 | |
| 			fs.update_metadata = false;
 | |
| 			break;
 | |
| 
 | |
| 		case 'h':
 | |
| 			usage(cout);
 | |
| 			return 0;
 | |
| 
 | |
| 		case 'V':
 | |
| 			cout << THIN_PROVISIONING_TOOLS_VERSION << endl;
 | |
| 			return 0;
 | |
| 
 | |
| 		default:
 | |
| 			usage(cerr);
 | |
| 			return 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fs.calc_sort_buffer_size();
 | |
| 
 | |
| 	if (argc != optind) {
 | |
| 		usage(cerr);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
|         if (!fs.metadata_dev) {
 | |
| 		cerr << "No metadata device provided.\n\n";
 | |
| 		usage(cerr);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if (!fs.origin_dev) {
 | |
| 		cerr << "No origin device provided.\n\n";
 | |
| 		usage(cerr);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if (!fs.fast_dev) {
 | |
| 		cerr << "No fast device provided.\n\n";
 | |
| 		usage(cerr);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	return writeback(fs);
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------
 |