Merge branch '2016-03-08-cache-writeback' into v0.7-devel
This commit is contained in:
		
							
								
								
									
										10
									
								
								Makefile.in
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Makefile.in
									
									
									
									
									
								
							| @@ -36,11 +36,15 @@ SOURCE=\ | ||||
| 	base/rolling_hash.cc \ | ||||
| 	base/xml_utils.cc \ | ||||
| 	block-cache/block_cache.cc \ | ||||
| 	block-cache/copier.cc \ | ||||
| 	block-cache/io_engine.cc \ | ||||
| 	block-cache/mem_pool.cc \ | ||||
| 	caching/cache_check.cc \ | ||||
| 	caching/cache_dump.cc \ | ||||
| 	caching/cache_metadata_size.cc \ | ||||
| 	caching/cache_repair.cc \ | ||||
| 	caching/cache_restore.cc \ | ||||
| 	caching/cache_writeback.cc \ | ||||
| 	caching/commands.cc \ | ||||
| 	caching/hint_array.cc \ | ||||
| 	caching/mapping_array.cc \ | ||||
| @@ -180,8 +184,8 @@ INSTALL_DATA = $(INSTALL) -p -m 644 | ||||
|  | ||||
| ifeq ("@TESTING@", "yes") | ||||
| TEST_INCLUDES=\ | ||||
| 	-Igmock-1.6.0/include \ | ||||
| 	-Igmock-1.6.0/gtest/include | ||||
| 	-Igoogletest/googlemock/include \ | ||||
| 	-Igoogletest/googletest/include | ||||
| else | ||||
| TEST_INCLUDES= | ||||
| endif | ||||
| @@ -234,6 +238,7 @@ install: bin/pdata_tools | ||||
| 	ln -s -f pdata_tools $(BINDIR)/cache_metadata_size | ||||
| 	ln -s -f pdata_tools $(BINDIR)/cache_repair | ||||
| 	ln -s -f pdata_tools $(BINDIR)/cache_restore | ||||
| 	ln -s -f pdata_tools $(BINDIR)/cache_writeback | ||||
| 	ln -s -f pdata_tools $(BINDIR)/thin_check | ||||
| 	ln -s -f pdata_tools $(BINDIR)/thin_delta | ||||
| 	ln -s -f pdata_tools $(BINDIR)/thin_dump | ||||
| @@ -253,6 +258,7 @@ install: bin/pdata_tools | ||||
| 	$(INSTALL_DATA) man8/cache_dump.8 $(MANPATH)/man8 | ||||
| 	$(INSTALL_DATA) man8/cache_repair.8 $(MANPATH)/man8 | ||||
| 	$(INSTALL_DATA) man8/cache_restore.8 $(MANPATH)/man8 | ||||
| 	$(INSTALL_DATA) man8/cache_writeback.8 $(MANPATH)/man8 | ||||
| 	$(INSTALL_DATA) man8/thin_check.8 $(MANPATH)/man8 | ||||
| 	$(INSTALL_DATA) man8/thin_delta.8 $(MANPATH)/man8 | ||||
| 	$(INSTALL_DATA) man8/thin_dump.8 $(MANPATH)/man8 | ||||
|   | ||||
							
								
								
									
										67
									
								
								base/unique_handle.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								base/unique_handle.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #ifndef BASE_UNIQUE_HANDLE_H | ||||
| #define BASE_UNIQUE_HANDLE_H | ||||
|  | ||||
| #include <list> | ||||
| #include <memory> | ||||
| #include <unistd.h> | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace base { | ||||
| 	template <typename T, T TNul = T()> | ||||
| 	class unique_handle | ||||
| 	{ | ||||
| 	public: | ||||
| 		unique_handle(std::nullptr_t = nullptr) | ||||
| 			: id_(TNul) { | ||||
| 		} | ||||
|  | ||||
| 		unique_handle(T x) | ||||
| 		: id_(x) { | ||||
| 		} | ||||
|  | ||||
| 		explicit operator bool() const { | ||||
| 			return id_ != TNul; | ||||
| 		} | ||||
|  | ||||
| 		operator T&() { | ||||
| 			return id_; | ||||
| 		} | ||||
|  | ||||
| 		operator T() const { | ||||
| 			return id_; | ||||
| 		} | ||||
|  | ||||
| 		T *operator&() { | ||||
| 			return &id_; | ||||
| 		} | ||||
|  | ||||
| 		const T *operator&() const { | ||||
| 			return &id_; | ||||
| 		} | ||||
|  | ||||
| 		friend bool operator == (unique_handle a, unique_handle b) { return a.id_ == b.id_; } | ||||
| 		friend bool operator != (unique_handle a, unique_handle b) { return a.id_ != b.id_; } | ||||
| 		friend bool operator == (unique_handle a, std::nullptr_t) { return a.id_ == TNul; } | ||||
| 		friend bool operator != (unique_handle a, std::nullptr_t) { return a.id_ != TNul; } | ||||
| 		friend bool operator == (std::nullptr_t, unique_handle b) { return TNul == b.id_; } | ||||
| 		friend bool operator != (std::nullptr_t, unique_handle b) { return TNul != b.id_; } | ||||
|  | ||||
| 	private: | ||||
| 		T id_; | ||||
| 	}; | ||||
|  | ||||
| 	//-------------------------------- | ||||
|  | ||||
| 	struct fd_deleter { | ||||
| 		typedef unique_handle<int, -1> pointer; | ||||
| 		void operator()(pointer p) { | ||||
| 			::close(p); | ||||
| 		} | ||||
| 	}; | ||||
| 	typedef std::unique_ptr<int, fd_deleter> unique_fd; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| #endif | ||||
| @@ -525,8 +525,11 @@ block_cache::get(block_address index, unsigned flags, validator::ptr v) | ||||
| 	block *b = lookup_or_read_block(index, flags, v); | ||||
|  | ||||
| 	if (b) { | ||||
| 		if (b->ref_count_ && flags & (GF_DIRTY | GF_ZERO)) | ||||
| 			throw std::runtime_error("attempt to write lock block concurrently"); | ||||
| 		if (b->ref_count_ && (flags & (GF_DIRTY | GF_ZERO))) { | ||||
| 			std::ostringstream out; | ||||
| 			out << "attempt to write lock block " << index << " concurrently"; | ||||
| 			throw std::runtime_error(out.str()); | ||||
| 		} | ||||
|  | ||||
| 		// FIXME: this gets called even for new blocks | ||||
| 		hit(*b); | ||||
|   | ||||
							
								
								
									
										192
									
								
								block-cache/copier.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								block-cache/copier.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| #include "block-cache/copier.h" | ||||
|  | ||||
| #include <stdexcept> | ||||
|  | ||||
| using namespace bcache; | ||||
| using namespace boost; | ||||
| using namespace std; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| copier::copier(io_engine &engine, | ||||
| 	       string const &src, string const &dest, | ||||
| 	       sector_t block_size, size_t mem) | ||||
| 	: pool_(block_size * 512, mem, PAGE_SIZE), | ||||
| 	  block_size_(block_size), | ||||
| 	  nr_blocks_(mem / block_size), | ||||
| 	  engine_(engine), | ||||
| 	  src_handle_(engine_.open_file(src, io_engine::M_READ_ONLY)), | ||||
| 	  dest_handle_(engine_.open_file(dest, io_engine::M_READ_WRITE)), | ||||
| 	  genkey_count_(0) | ||||
| { | ||||
| } | ||||
|  | ||||
| copier::~copier() | ||||
| { | ||||
| 	engine_.close_file(src_handle_); | ||||
| 	engine_.close_file(dest_handle_); | ||||
| } | ||||
|  | ||||
| void | ||||
| copier::issue(copy_op const &op) | ||||
| { | ||||
| 	void *data; | ||||
|  | ||||
| 	while (!(data = pool_.alloc())) { | ||||
| 		wait_(); | ||||
|  | ||||
| 		// data may still not be present because the wait_ could | ||||
| 		// have completed a read and issued the corresponding | ||||
| 		// write. | ||||
| 	} | ||||
|  | ||||
| 	copy_job job(op, data); | ||||
| 	job.op.read_complete = job.op.write_complete = false; | ||||
| 	unsigned key = genkey(); // used as context for the io_engine | ||||
|  | ||||
| 	auto r = engine_.issue_io(src_handle_, | ||||
| 				  io_engine::D_READ, | ||||
| 				  to_sector(op.src_b), | ||||
| 				  to_sector(op.src_e), | ||||
| 				  data, | ||||
| 				  key); | ||||
|  | ||||
| 	if (r) | ||||
| 		jobs_.insert(make_pair(key, job)); | ||||
|  | ||||
| 	else | ||||
| 		complete(job); | ||||
| } | ||||
|  | ||||
| unsigned | ||||
| copier::nr_pending() const | ||||
| { | ||||
| 	return jobs_.size() + complete_.size(); | ||||
| } | ||||
|  | ||||
| boost::optional<copy_op> | ||||
| copier::wait() | ||||
| { | ||||
| 	if (complete_.empty()) | ||||
| 		wait_(); | ||||
|  | ||||
| 	return wait_complete(); | ||||
| } | ||||
|  | ||||
| boost::optional<copy_op> | ||||
| copier::wait(unsigned µ) | ||||
| { | ||||
| 	if (complete_.empty()) | ||||
| 		wait_(micro); | ||||
| 	return wait_complete(); | ||||
| } | ||||
|  | ||||
| bool | ||||
| copier::pending() const | ||||
| { | ||||
| 	return !jobs_.empty(); | ||||
| } | ||||
|  | ||||
| boost::optional<copy_op> | ||||
| copier::wait_complete() | ||||
| { | ||||
| 	if (complete_.empty()) { | ||||
| 		return optional<copy_op>(); | ||||
|  | ||||
| 	} else { | ||||
| 		auto op = complete_.front(); | ||||
| 		complete_.pop_front(); | ||||
| 		return optional<copy_op>(op); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void | ||||
| copier::wait_(unsigned µ) | ||||
| { | ||||
| 	optional<io_engine::wait_result> mp; | ||||
|  | ||||
| 	if (!pending()) | ||||
| 		return; | ||||
|  | ||||
|  | ||||
| 	bool completed = false; | ||||
| 	while (pending() && !completed) { | ||||
| 		mp = engine_.wait(micro); | ||||
| 		if (mp) | ||||
| 			completed = wait_successful(*mp); | ||||
|  | ||||
| 		if (!micro) | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void | ||||
| copier::wait_() | ||||
| { | ||||
| 	bool completed = false; | ||||
|  | ||||
| 	while (pending() && !completed) { | ||||
| 		auto mp = engine_.wait(); | ||||
| 		if (mp) | ||||
| 			completed = wait_successful(*mp); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool | ||||
| copier::wait_successful(io_engine::wait_result const &p) | ||||
| { | ||||
| 	auto it = jobs_.find(p.second); | ||||
| 	if (it == jobs_.end()) | ||||
| 		throw runtime_error("Internal error.  Lost track of copy job."); | ||||
|  | ||||
| 	copy_job &j = it->second; | ||||
| 	if (!p.first) { | ||||
| 		// IO was unsuccessful | ||||
| 		complete(j); | ||||
| 		jobs_.erase(it); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	// IO was successful | ||||
| 	if (!j.op.read_complete) { | ||||
| 		j.op.read_complete = true; | ||||
| 		if (!engine_.issue_io(dest_handle_, | ||||
| 				      io_engine::D_WRITE, | ||||
| 				      to_sector(j.op.dest_b), | ||||
| 				      to_sector(j.op.dest_b + (j.op.src_e - j.op.src_b)), | ||||
| 				      j.data, | ||||
| 				      it->first)) { | ||||
| 			complete(j); | ||||
| 			jobs_.erase(it); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
|  | ||||
| 	} else { | ||||
| 		j.op.write_complete = true; | ||||
| 		complete(j); | ||||
| 		jobs_.erase(it); | ||||
| 		return true; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void | ||||
| copier::complete(copy_job const &j) | ||||
| { | ||||
| 	pool_.free(j.data); | ||||
| 	complete_.push_back(j.op); | ||||
| } | ||||
|  | ||||
| sector_t | ||||
| copier::to_sector(block_address b) const | ||||
| { | ||||
| 	return b * block_size_; | ||||
| } | ||||
|  | ||||
| unsigned | ||||
| copier::genkey() | ||||
| { | ||||
| 	return genkey_count_++; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
							
								
								
									
										106
									
								
								block-cache/copier.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								block-cache/copier.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| #ifndef BLOCK_CACHE_COPIER_H | ||||
| #define BLOCK_CACHE_COPIER_H | ||||
|  | ||||
| #include "block-cache/io_engine.h" | ||||
| #include "block-cache/mem_pool.h" | ||||
|  | ||||
| #include <string> | ||||
| #include <list> | ||||
| #include <map> | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace bcache { | ||||
| 	using block_address = uint64_t; | ||||
|  | ||||
| 	struct copy_op { | ||||
| 		copy_op() | ||||
| 			: src_b(0), | ||||
| 			  src_e(0), | ||||
| 			  dest_b(0), | ||||
| 			  read_complete(false), | ||||
| 			  write_complete(false) { | ||||
| 		} | ||||
|  | ||||
| 		copy_op(block_address src_b_, | ||||
| 			block_address src_e_, | ||||
| 			block_address dest_b_) | ||||
| 			: src_b(src_b_), | ||||
| 			  src_e(src_e_), | ||||
| 			  dest_b(dest_b_), | ||||
| 			  read_complete(false), | ||||
| 			  write_complete(false) { | ||||
| 		} | ||||
|  | ||||
| 		bool operator <(copy_op const &rhs) const { | ||||
| 			return dest_b < rhs.dest_b; | ||||
| 		} | ||||
|  | ||||
| 		bool success() const { | ||||
| 			return read_complete && write_complete; | ||||
| 		} | ||||
|  | ||||
| 		block_address src_b, src_e; | ||||
| 		block_address dest_b; | ||||
|  | ||||
| 		bool read_complete; | ||||
| 		bool write_complete; | ||||
| 	}; | ||||
|  | ||||
| 	class copy_job { | ||||
| 	public: | ||||
| 		copy_job(copy_op const &op_, void *data_) | ||||
| 			: op(op_), data(data_) { | ||||
| 		} | ||||
|  | ||||
| 		copy_op op; | ||||
| 		void *data; | ||||
| 	}; | ||||
|  | ||||
| 	class copier { | ||||
| 	public: | ||||
| 		copier(io_engine &engine, | ||||
| 		       std::string const &src, std::string const &dest, | ||||
| 		       sector_t block_size, size_t mem); | ||||
| 		~copier(); | ||||
|  | ||||
| 		sector_t get_block_size() const { | ||||
| 			return block_size_; | ||||
| 		} | ||||
|  | ||||
| 		// Blocks if out of memory. | ||||
| 		void issue(copy_op const &op); | ||||
|  | ||||
| 		unsigned nr_pending() const; | ||||
| 		boost::optional<copy_op> wait(); | ||||
| 		boost::optional<copy_op> wait(unsigned µ); | ||||
|  | ||||
| 	private: | ||||
| 		bool pending() const; | ||||
| 		bool wait_successful(io_engine::wait_result const &p); | ||||
| 		boost::optional<copy_op> wait_complete(); | ||||
| 		void wait_(unsigned µ); | ||||
| 		void wait_(); | ||||
| 		void complete(copy_job const &j); | ||||
|  | ||||
| 		sector_t to_sector(block_address b) const; | ||||
| 		unsigned genkey(); | ||||
|  | ||||
| 		mempool pool_; | ||||
| 		sector_t block_size_; | ||||
| 		unsigned nr_blocks_; | ||||
| 		io_engine &engine_; | ||||
| 		io_engine::handle src_handle_; | ||||
| 		io_engine::handle dest_handle_; | ||||
| 		unsigned genkey_count_; | ||||
|  | ||||
| 		using job_map = std::map<unsigned, copy_job>; | ||||
| 		using op_list = std::list<copy_op>; | ||||
| 		job_map jobs_; | ||||
| 		op_list complete_; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										199
									
								
								block-cache/io_engine.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								block-cache/io_engine.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| #include "base/container_of.h" | ||||
| #include "block-cache/io_engine.h" | ||||
|  | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <sstream> | ||||
| #include <stdexcept> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/types.h> | ||||
|  | ||||
| using namespace bcache; | ||||
| using namespace boost; | ||||
| using namespace std; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| control_block_set::control_block_set(unsigned nr) | ||||
| 	: cbs_(nr) | ||||
| { | ||||
| 	for (auto i = 0u; i < nr; i++) | ||||
| 		free_cbs_.insert(i); | ||||
| } | ||||
|  | ||||
| iocb * | ||||
| control_block_set::alloc(unsigned context) | ||||
| { | ||||
| 	if (free_cbs_.empty()) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	auto it = free_cbs_.begin(); | ||||
|  | ||||
| 	cblock &cb = cbs_[*it]; | ||||
| 	cb.context = context; | ||||
| 	free_cbs_.erase(it); | ||||
|  | ||||
| 	return &cb.cb; | ||||
| } | ||||
|  | ||||
| void | ||||
| control_block_set::free(iocb *cb) | ||||
| { | ||||
| 	cblock *b = base::container_of(cb, &cblock::cb); | ||||
| 	unsigned index = b - &cbs_[0]; | ||||
| 	free_cbs_.insert(index); | ||||
| } | ||||
|  | ||||
| unsigned | ||||
| control_block_set::context(iocb *cb) const | ||||
| { | ||||
| 	cblock *b = base::container_of(cb, &cblock::cb); | ||||
| 	return b->context; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| aio_engine::aio_engine(unsigned max_io) | ||||
| 	: aio_context_(0), | ||||
| 	  cbs_(max_io) | ||||
| { | ||||
| 	int r = io_setup(max_io, &aio_context_); | ||||
| 	if (r < 0) | ||||
| 		throw runtime_error("io_setup failed"); | ||||
| } | ||||
|  | ||||
| aio_engine::~aio_engine() | ||||
| { | ||||
| 	io_destroy(aio_context_); | ||||
| } | ||||
|  | ||||
| aio_engine::handle | ||||
| aio_engine::open_file(std::string const &path, mode m, sharing s) | ||||
| { | ||||
| 	int flags = (m == M_READ_ONLY) ? O_RDONLY : O_RDWR; | ||||
| 	if (s == EXCLUSIVE) | ||||
| 		flags |= O_EXCL; | ||||
| 	int fd = ::open(path.c_str(), O_DIRECT | flags); | ||||
| 	if (fd < 0) { | ||||
| 		ostringstream out; | ||||
| 		out << "unable to open '" << path << "'"; | ||||
| 		throw runtime_error(out.str()); | ||||
| 	} | ||||
|  | ||||
| 	descriptors_.push_back(base::unique_fd(fd)); | ||||
|  | ||||
| 	return static_cast<handle>(fd); | ||||
| } | ||||
|  | ||||
| void | ||||
| aio_engine::close_file(handle h) | ||||
| { | ||||
| 	for (auto it = descriptors_.begin(); it != descriptors_.end(); ++it) { | ||||
| 		unsigned it_h = it->get(); | ||||
| 		if (it_h == h) { | ||||
| 			descriptors_.erase(it); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ostringstream out; | ||||
| 	out << "unknown descriptor (" << h << ")"; | ||||
| 	throw runtime_error(out.str()); | ||||
| } | ||||
|  | ||||
| bool | ||||
| aio_engine::issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context) | ||||
| { | ||||
| 	if (reinterpret_cast<uint64_t>(data) & (PAGE_SIZE - 1)) | ||||
| 		throw runtime_error("Data passed to issue_io must be page aligned\n"); | ||||
|  | ||||
| 	iocb *cb; | ||||
|  | ||||
| 	cb = cbs_.alloc(context); | ||||
| 	if (!cb) | ||||
| 		return false; | ||||
|  | ||||
| 	memset(cb, 0, sizeof(*cb)); | ||||
|  | ||||
| 	cb->aio_fildes = static_cast<int>(h); | ||||
| 	cb->u.c.buf = data; | ||||
| 	cb->u.c.offset = b << SECTOR_SHIFT; | ||||
| 	cb->u.c.nbytes = (e - b) << SECTOR_SHIFT; | ||||
| 	cb->aio_lio_opcode = (d == D_READ) ? IO_CMD_PREAD : IO_CMD_PWRITE; | ||||
|  | ||||
| 	int r = io_submit(aio_context_, 1, &cb); | ||||
| 	return r == 1; | ||||
| } | ||||
|  | ||||
| optional<io_engine::wait_result> | ||||
| aio_engine::wait() | ||||
| { | ||||
| 	return wait_(NULL); | ||||
| } | ||||
|  | ||||
| optional<io_engine::wait_result> | ||||
| aio_engine::wait(unsigned µsec) | ||||
| { | ||||
| 	timespec start = micro_to_ts(microsec); | ||||
| 	timespec stop = start; | ||||
| 	auto r = wait_(&stop); | ||||
| 	microsec = ts_to_micro(stop) - microsec; | ||||
| 	return r; | ||||
| } | ||||
|  | ||||
| boost::optional<io_engine::wait_result> | ||||
| aio_engine::wait_(timespec *ts) | ||||
| { | ||||
| 	int r; | ||||
| 	struct io_event event; | ||||
|  | ||||
| 	memset(&event, 0, sizeof(event)); | ||||
| 	r = io_getevents(aio_context_, 1, 1, &event, ts); | ||||
| 	if (r < 0) { | ||||
| 		std::ostringstream out; | ||||
| 		out << "io_getevents failed: " << r; | ||||
| 		throw std::runtime_error(out.str()); | ||||
| 	} | ||||
|  | ||||
| 	if (r == 0) { | ||||
| 		return optional<wait_result>(); | ||||
| 	} | ||||
|  | ||||
| 	iocb *cb = reinterpret_cast<iocb *>(event.obj); | ||||
| 	unsigned context = cbs_.context(cb); | ||||
|  | ||||
| 	if (event.res == cb->u.c.nbytes) { | ||||
| 		cbs_.free(cb); | ||||
| 		return optional<wait_result>(make_pair(true, context)); | ||||
|  | ||||
| 	} else if (static_cast<int>(event.res) < 0) { | ||||
| 		cbs_.free(cb); | ||||
| 		return optional<wait_result>(make_pair(false, context)); | ||||
|  | ||||
| 	} else { | ||||
| 		cbs_.free(cb); | ||||
| 		return optional<wait_result>(make_pair(false, context)); | ||||
| 	} | ||||
|  | ||||
| 	// shouldn't get here | ||||
| 	return optional<wait_result>(make_pair(false, 0)); | ||||
| } | ||||
|  | ||||
| struct timespec | ||||
| aio_engine::micro_to_ts(unsigned micro) | ||||
| { | ||||
| 	timespec ts; | ||||
| 	ts.tv_sec = micro / 1000000u; | ||||
| 	ts.tv_nsec = (micro % 1000000) * 1000; | ||||
| 	return ts; | ||||
| } | ||||
|  | ||||
| unsigned | ||||
| aio_engine::ts_to_micro(timespec const &ts) | ||||
| { | ||||
| 	unsigned micro = ts.tv_sec * 1000000; | ||||
| 	micro += ts.tv_nsec / 1000; | ||||
| 	return micro; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
							
								
								
									
										117
									
								
								block-cache/io_engine.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								block-cache/io_engine.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| #ifndef BLOCK_CACHE_IO_ENGINE_H | ||||
| #define BLOCK_CACHE_IO_ENGINE_H | ||||
|  | ||||
| #include "base/unique_handle.h" | ||||
|  | ||||
| #include <boost/optional.hpp> | ||||
| #include <ctype.h> | ||||
| #include <set> | ||||
| #include <string> | ||||
| #include <libaio.h> | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace bcache { | ||||
| 	using sector_t = uint64_t; | ||||
|  | ||||
| 	unsigned const SECTOR_SHIFT = 9; | ||||
| 	unsigned const PAGE_SIZE = 4096; | ||||
|  | ||||
| 	// Virtual base class to aid unit testing | ||||
| 	class io_engine { | ||||
| 	public: | ||||
| 		enum mode { | ||||
| 			M_READ_ONLY, | ||||
| 			M_READ_WRITE | ||||
| 		}; | ||||
|  | ||||
| 		enum dir { | ||||
| 			D_READ, | ||||
| 			D_WRITE | ||||
| 		}; | ||||
|  | ||||
| 		enum sharing { | ||||
| 			EXCLUSIVE, | ||||
| 			SHARED | ||||
| 		}; | ||||
|  | ||||
| 		io_engine() {} | ||||
| 		virtual ~io_engine() {} | ||||
|  | ||||
| 		using handle = unsigned; | ||||
|  | ||||
| 		virtual handle open_file(std::string const &path, mode m, sharing s = EXCLUSIVE) = 0; | ||||
| 		virtual void close_file(handle h) = 0; | ||||
|  | ||||
| 		// returns false if there are insufficient resources to | ||||
| 		// queue the IO | ||||
| 		virtual bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context) = 0; | ||||
|  | ||||
| 		// returns (success, context) | ||||
| 		using wait_result = std::pair<bool, unsigned>; | ||||
| 		virtual boost::optional<wait_result> wait() = 0; | ||||
| 		virtual boost::optional<wait_result> wait(unsigned µsec) = 0; | ||||
|  | ||||
| 	private: | ||||
| 		io_engine(io_engine const &) = delete; | ||||
| 		io_engine &operator =(io_engine const &) = delete; | ||||
| 	}; | ||||
|  | ||||
| 	//-------------------------------- | ||||
|  | ||||
| 	class control_block_set { | ||||
| 	public: | ||||
| 		control_block_set(unsigned nr); | ||||
|  | ||||
| 		iocb *alloc(unsigned context); | ||||
| 		void free(iocb *); | ||||
|  | ||||
| 		unsigned context(iocb *) const; | ||||
|  | ||||
| 	private: | ||||
| 		struct cblock { | ||||
| 			unsigned context; | ||||
| 			struct iocb cb; | ||||
| 		}; | ||||
|  | ||||
| 		std::set<unsigned> free_cbs_; | ||||
| 		std::vector<cblock> cbs_; | ||||
| 	}; | ||||
|  | ||||
| 	//---------------- | ||||
|  | ||||
| 	class aio_engine : public io_engine { | ||||
| 	public: | ||||
| 		// max_io is the maximum nr of concurrent ios expected | ||||
| 		aio_engine(unsigned max_io); | ||||
| 		~aio_engine(); | ||||
|  | ||||
| 		using handle = unsigned; | ||||
|  | ||||
| 		virtual handle open_file(std::string const &path, mode m, sharing s = EXCLUSIVE); | ||||
| 		virtual void close_file(handle h); | ||||
|  | ||||
| 		// Returns false if queueing the io failed | ||||
| 		virtual bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context); | ||||
|  | ||||
| 		virtual boost::optional<wait_result> wait(); | ||||
| 		virtual boost::optional<wait_result> wait(unsigned µsec); | ||||
|  | ||||
| 	private: | ||||
| 		static struct timespec micro_to_ts(unsigned micro); | ||||
| 		static unsigned ts_to_micro(timespec const &ts); | ||||
| 		boost::optional<io_engine::wait_result> wait_(timespec *ts); | ||||
|  | ||||
| 		std::list<base::unique_fd> descriptors_; | ||||
|  | ||||
| 		io_context_t aio_context_; | ||||
| 		control_block_set cbs_; | ||||
|  | ||||
| 		aio_engine(io_engine const &) = delete; | ||||
| 		aio_engine &operator =(io_engine const &) = delete; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										62
									
								
								block-cache/mem_pool.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								block-cache/mem_pool.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| #include "block-cache/mem_pool.h" | ||||
|  | ||||
| #include <sstream> | ||||
| #include <stdexcept> | ||||
| #include <stdlib.h> | ||||
|  | ||||
| using namespace bcache; | ||||
| using namespace boost; | ||||
| using namespace mempool_detail; | ||||
| using namespace std; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| mempool::mempool(size_t block_size, size_t total_mem, size_t alignment) | ||||
| { | ||||
| 	mem_ = alloc_aligned(total_mem, alignment); | ||||
|  | ||||
| 	unsigned nr_blocks = total_mem / block_size; | ||||
| 	for (auto i = 0u; i < nr_blocks; i++) | ||||
| 		free(static_cast<unsigned char *>(mem_) + (block_size * i)); | ||||
| } | ||||
|  | ||||
| mempool::~mempool() | ||||
| { | ||||
| 	free_.clear(); | ||||
| 	::free(mem_); | ||||
| } | ||||
|  | ||||
| void * | ||||
| mempool::alloc() | ||||
| { | ||||
| 	if (free_.empty()) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	mempool_detail::alloc_block &b = free_.front(); | ||||
| 	free_.pop_front(); | ||||
| 	return reinterpret_cast<void *>(&b); | ||||
| } | ||||
|  | ||||
| void | ||||
| mempool::free(void *data) | ||||
| { | ||||
| 	mempool_detail::alloc_block *b = reinterpret_cast<mempool_detail::alloc_block *>(data); | ||||
| 	free_.push_front(*b); | ||||
| } | ||||
|  | ||||
| void * | ||||
| mempool::alloc_aligned(size_t len, size_t alignment) | ||||
| { | ||||
| 	void *result = NULL; | ||||
| 	int r = posix_memalign(&result, alignment, len); | ||||
| 	if (r) { | ||||
| 		ostringstream out; | ||||
| 		out << "posix_memalign failed: len = " << len << ", alignment = " << alignment << ", r = " << r << "\n"; | ||||
| 		throw runtime_error(out.str()); | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
							
								
								
									
										46
									
								
								block-cache/mem_pool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								block-cache/mem_pool.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #ifndef BLOCK_CACHE_MEM_POOL_H | ||||
| #define BLOCK_CACHE_MEM_POOL_H | ||||
|  | ||||
| #include <boost/intrusive/list.hpp> | ||||
| #include <boost/optional.hpp> | ||||
| #include <list> | ||||
|  | ||||
| namespace bi = boost::intrusive; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace bcache { | ||||
| 	// FIXME: move to base? | ||||
|  | ||||
| 	namespace mempool_detail { | ||||
| 		struct alloc_block : public bi::list_base_hook<bi::link_mode<bi::normal_link>> { | ||||
| 		}; | ||||
| 	}; | ||||
|  | ||||
| 	class mempool { | ||||
| 	public: | ||||
| 		// alignment must be a power of 2 | ||||
| 		mempool(size_t block_size, size_t total_mem, size_t alignment = 8); | ||||
| 		~mempool(); | ||||
|  | ||||
| 		void *alloc(); | ||||
| 		void free(void *data); | ||||
|  | ||||
| 	private: | ||||
| 		static void *alloc_aligned(size_t len, size_t alignment); | ||||
|  | ||||
| 		using block_list = bi::list<mempool_detail::alloc_block>; | ||||
|  | ||||
| 		void *mem_; | ||||
| 		block_list free_; | ||||
|  | ||||
| 		//---------------- | ||||
|  | ||||
| 		mempool(mempool const &) = delete; | ||||
| 		mempool &operator =(mempool const &) = delete; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										459
									
								
								caching/cache_writeback.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										459
									
								
								caching/cache_writeback.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,459 @@ | ||||
| #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> | ||||
|  | ||||
| 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, metadata::OPEN); | ||||
|  | ||||
| 		// 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); | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
| @@ -13,6 +13,7 @@ caching::register_cache_commands(application &app) | ||||
| 	app.add_cmd(command::ptr(new cache_metadata_size_cmd)); | ||||
| 	app.add_cmd(command::ptr(new cache_restore_cmd)); | ||||
| 	app.add_cmd(command::ptr(new cache_repair_cmd)); | ||||
| 	app.add_cmd(command::ptr(new cache_writeback_cmd)); | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|   | ||||
| @@ -63,6 +63,13 @@ namespace caching { | ||||
| 		virtual int run(int argc, char **argv); | ||||
| 	}; | ||||
|  | ||||
| 	class cache_writeback_cmd : public base::command { | ||||
| 	public: | ||||
| 		cache_writeback_cmd(); | ||||
| 		virtual void usage(std::ostream &out) const; | ||||
| 		virtual int run(int argc, char **argv); | ||||
| 	}; | ||||
|  | ||||
| 	void register_cache_commands(base::application &app); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| #!/bin/sh -e | ||||
|  | ||||
| wget https://googlemock.googlecode.com/files/gmock-1.6.0.zip | ||||
| unzip gmock-1.6.0.zip | ||||
| cd gmock-1.6.0 | ||||
| ./configure | ||||
| git clone https://github.com/google/googletest | ||||
|   | ||||
							
								
								
									
										53
									
								
								man8/cache_writeback.8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								man8/cache_writeback.8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| .TH CACHE_WRITEBACK 8 "Thin Provisioning Tools" "Red Hat, Inc." \" -*- nroff -*- | ||||
| .SH NAME | ||||
| cache_writeback \- writeback dirty blocks to the origin device. | ||||
|  | ||||
| .SH SYNOPSIS | ||||
| .B cache_writeback | ||||
| .RB [ options ] | ||||
| .RB --metadata-device | ||||
| .I {device|file} | ||||
| .RB --origin-device | ||||
| .I {device|file} | ||||
| .RB --fast-device | ||||
| .I {device|file} | ||||
|  | ||||
| .SH DESCRIPTION | ||||
| .B cache_writeback | ||||
|  | ||||
| An offline tool that writesback dirty data to the data device | ||||
| (origin).  Intended for use in recovery scenarios when the SSD is | ||||
| giving IO errors. | ||||
|  | ||||
| This tool cannot be run on a live cache. | ||||
|  | ||||
| .SH OPTIONS | ||||
|  | ||||
| .IP "\fB\\-\-metadata\-device\fP \fI{device|file}\fP" | ||||
| Location of cache metadata. | ||||
|  | ||||
| .IP "\fB\-\-origin\-device\fP \fI{device|file}\fP" | ||||
| Slow device being cached. | ||||
|  | ||||
| .IP "\fB\-\-fast\-device\fP \fI{device|file}\fP" | ||||
| Fast device containing the data that needs to be written back. | ||||
|  | ||||
| .IP "\fB\-\-skip\-metadata\-update\fP" | ||||
| Do not update the metadata to clear the dirty flags for written back | ||||
| data.  You may not want to do this if you're decommissioning the | ||||
| cache. | ||||
|  | ||||
| .IP "\fB\-h, \-\-help\fP" | ||||
| Print help and exit. | ||||
|  | ||||
| .IP "\fB\-V, \-\-version\fP" | ||||
| Output version information and exit. | ||||
|  | ||||
| .SH SEE ALSO | ||||
| .B cache_dump(8) | ||||
| .B cache_check(8) | ||||
| .B cache_repair(8) | ||||
| .B cache_restore(8) | ||||
|  | ||||
| .SH AUTHOR | ||||
| Joe Thornber <ejt@redhat.com> | ||||
| @@ -19,6 +19,7 @@ | ||||
| #include "block.h" | ||||
|  | ||||
| #include "base/error_string.h" | ||||
| #include "block-cache/io_engine.h" | ||||
|  | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| @@ -38,8 +39,6 @@ namespace { | ||||
| 	using namespace std; | ||||
|  | ||||
| 	int const DEFAULT_MODE = 0666; | ||||
| 	unsigned const SECTOR_SHIFT = 9; | ||||
|  | ||||
| 	int const OPEN_FLAGS = O_DIRECT; | ||||
|  | ||||
| 	// FIXME: introduce a new exception for this, or at least lift this | ||||
|   | ||||
| @@ -66,6 +66,7 @@ namespace thin_provisioning { | ||||
| 	class thin_trim_cmd : public base::command { | ||||
| 	public: | ||||
| 		thin_trim_cmd(); | ||||
|  | ||||
| 		virtual void usage(std::ostream &out) const; | ||||
| 		virtual int run(int argc, char **argv); | ||||
| 	}; | ||||
| @@ -74,6 +75,7 @@ namespace thin_provisioning { | ||||
| 	class thin_ll_dump_cmd : public base::command { | ||||
| 	public: | ||||
| 		thin_ll_dump_cmd(); | ||||
|  | ||||
| 		virtual void usage(std::ostream &out) const; | ||||
| 		virtual int run(int argc, char **argv); | ||||
| 	}; | ||||
| @@ -81,6 +83,7 @@ namespace thin_provisioning { | ||||
| 	class thin_ll_restore_cmd : public base::command { | ||||
| 	public: | ||||
| 		thin_ll_restore_cmd(); | ||||
|  | ||||
| 		virtual void usage(std::ostream &out) const; | ||||
| 		virtual int run(int argc, char **argv); | ||||
| 	}; | ||||
| @@ -88,6 +91,7 @@ namespace thin_provisioning { | ||||
| 	class thin_scan_cmd : public base::command { | ||||
| 	public: | ||||
| 		thin_scan_cmd(); | ||||
|  | ||||
| 		virtual void usage(std::ostream &out) const; | ||||
| 		virtual int run(int argc, char **argv); | ||||
| 	}; | ||||
|   | ||||
| @@ -16,10 +16,10 @@ | ||||
| # with thin-provisioning-tools.  If not, see | ||||
| # <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| GMOCK_DIR=gmock-1.6.0/ | ||||
| GMOCK_DIR=googletest | ||||
| GMOCK_INCLUDES=\ | ||||
| 	-Igmock-1.6.0/include \ | ||||
| 	-Igmock-1.6.0/gtest/include | ||||
| 	-I$(GMOCK_DIR)/googlemock/include \ | ||||
| 	-I$(GMOCK_DIR)/googletest/include | ||||
|  | ||||
| GMOCK_FLAGS=\ | ||||
| 	-Wno-unused-local-typedefs | ||||
| @@ -28,16 +28,16 @@ GMOCK_LIBS=\ | ||||
| 	-Llib -lpdata -lgmock -lpthread -laio | ||||
|  | ||||
| GMOCK_DEPS=\ | ||||
| 	$(wildcard $(GMOCK_DIR)/include/*.h) \ | ||||
| 	$(wildcard $(GMOCK_DIR)/src/*.cc) \ | ||||
| 	$(wildcard $(GMOCK_DIR)/gtest/include/*.h) \ | ||||
| 	$(wildcard $(GMOCK_DIR)/gtest/src/*.cc) | ||||
| 	$(wildcard $(GMOCK_DIR)/googlemock/include/*.h) \ | ||||
| 	$(wildcard $(GMOCK_DIR)/googlemock/src/*.cc) \ | ||||
| 	$(wildcard $(GMOCK_DIR)/googletest/include/*.h) \ | ||||
| 	$(wildcard $(GMOCK_DIR)/googletest/src/*.cc) | ||||
|  | ||||
| lib/libgmock.a: $(GMOCK_DEPS) | ||||
| 	@echo "    [CXX] gtest" | ||||
| 	$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/gtest -c $(GMOCK_DIR)/gtest/src/gtest-all.cc | ||||
| 	$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/googletest -c $(GMOCK_DIR)/googletest/src/gtest-all.cc | ||||
| 	@echo "    [CXX] gmock" | ||||
| 	$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR) -c $(GMOCK_DIR)/src/gmock-all.cc | ||||
| 	$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/googlemock -c $(GMOCK_DIR)/googlemock/src/gmock-all.cc | ||||
| 	@echo "    [AR]  $<" | ||||
| 	$(V)ar -rv lib/libgmock.a gtest-all.o gmock-all.o > /dev/null 2>&1 | ||||
|  | ||||
| @@ -55,9 +55,12 @@ TEST_SOURCE=\ | ||||
| 	unit-tests/btree_counter_t.cc \ | ||||
| 	unit-tests/btree_damage_visitor_t.cc \ | ||||
| 	unit-tests/cache_superblock_t.cc \ | ||||
| 	unit-tests/copier_t.cc \ | ||||
| 	unit-tests/damage_tracker_t.cc \ | ||||
| 	unit-tests/endian_t.cc \ | ||||
| 	unit-tests/error_state_t.cc \ | ||||
| 	unit-tests/io_engine_t.cc \ | ||||
| 	unit-tests/mem_pool_t.cc \ | ||||
| 	unit-tests/rmap_visitor_t.cc \ | ||||
| 	unit-tests/rolling_hash_t.cc \ | ||||
| 	unit-tests/run_set_t.cc \ | ||||
|   | ||||
| @@ -64,6 +64,7 @@ namespace { | ||||
| 		typedef boost::shared_ptr<validator_mock> ptr; | ||||
|  | ||||
| 		MOCK_CONST_METHOD2(check, void(void const *, block_address)); | ||||
| 		MOCK_CONST_METHOD1(check_raw, bool(void const *data)); | ||||
| 		MOCK_CONST_METHOD2(prepare, void(void *, block_address)); | ||||
| 	}; | ||||
|  | ||||
|   | ||||
							
								
								
									
										273
									
								
								unit-tests/copier_t.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								unit-tests/copier_t.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,273 @@ | ||||
| // Copyright (C) 2016 Red Hat, Inc. All rights reserved. | ||||
| // | ||||
| // This file is part of the thin-provisioning-tools source. | ||||
| // | ||||
| // thin-provisioning-tools is free software: you can redistribute it | ||||
| // and/or modify it under the terms of the GNU General Public License | ||||
| // as published by the Free Software Foundation, either version 3 of | ||||
| // the License, or (at your option) any later version. | ||||
| // | ||||
| // thin-provisioning-tools is distributed in the hope that it will be | ||||
| // useful, but WITHOUT ANY WARRANTY; without even the implied warranty | ||||
| // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License along | ||||
| // with thin-provisioning-tools.  If not, see | ||||
| // <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| #include "gmock/gmock.h" | ||||
| #include "block-cache/copier.h" | ||||
| #include "test_utils.h" | ||||
|  | ||||
| #include <fcntl.h> | ||||
|  | ||||
| using namespace boost; | ||||
| using namespace std; | ||||
| using namespace test; | ||||
| using namespace testing; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace { | ||||
| 	unsigned const BLOCK_SIZE = 64u; | ||||
| 	using wait_result = io_engine::wait_result; | ||||
|  | ||||
| 	ostream &operator <<(ostream &out, wait_result const &wr) { | ||||
| 		out << "wait_result[" << wr.first << ", " << wr.second << "]"; | ||||
| 		return out; | ||||
| 	} | ||||
|  | ||||
| 	ostream &operator <<(ostream &out, optional<wait_result> const &mwr) { | ||||
| 		if (mwr) { | ||||
| 			out << "Just[wait_result[" << mwr->first << ", " << mwr->second << "]]"; | ||||
| 		} else | ||||
| 			out << "Nothing"; | ||||
| 		return out; | ||||
| 	} | ||||
|  | ||||
| 	class io_engine_mock : public io_engine { | ||||
| 	public: | ||||
| 		MOCK_METHOD3(open_file, handle(string const &, mode, sharing)); | ||||
| 		MOCK_METHOD1(close_file, void(handle)); | ||||
| 		MOCK_METHOD6(issue_io, bool(handle, dir, sector_t, sector_t, void *, unsigned)); | ||||
|  | ||||
| 		MOCK_METHOD0(wait, optional<wait_result>()); | ||||
| 		MOCK_METHOD1(wait, optional<wait_result>(unsigned &)); | ||||
| 	}; | ||||
|  | ||||
| 	class CopierTests : public Test { | ||||
| 	public: | ||||
| 		CopierTests() | ||||
| 			: src_file_("copy_src"), | ||||
| 			  dest_file_("copy_dest") { | ||||
| 		} | ||||
|  | ||||
| 		unique_ptr<copier> make_copier() { | ||||
| 			EXPECT_CALL(engine_, open_file(src_file_, io_engine::M_READ_ONLY, io_engine::EXCLUSIVE)). | ||||
| 				WillOnce(Return(SRC_HANDLE)); | ||||
| 			EXPECT_CALL(engine_, open_file(dest_file_, io_engine::M_READ_WRITE, io_engine::EXCLUSIVE)). | ||||
| 				WillOnce(Return(DEST_HANDLE)); | ||||
|  | ||||
| 			EXPECT_CALL(engine_, close_file(SRC_HANDLE)).Times(1); | ||||
| 			EXPECT_CALL(engine_, close_file(DEST_HANDLE)).Times(1); | ||||
|  | ||||
| 			return unique_ptr<copier>(new copier(engine_, src_file_, | ||||
| 							     dest_file_, | ||||
| 							     BLOCK_SIZE, 1 * 1024 * 1024)); | ||||
| 		} | ||||
|  | ||||
| 		static optional<wait_result> make_wr(bool success, unsigned context) { | ||||
| 			return optional<wait_result>(wait_result(success, context)); | ||||
| 		} | ||||
|  | ||||
| 		void issue_successful_op(copier &c, copy_op &op, unsigned context) { | ||||
| 			InSequence dummy; | ||||
|  | ||||
| 			unsigned nr_pending = c.nr_pending(); | ||||
| 			EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, | ||||
| 						      op.src_b * BLOCK_SIZE, | ||||
| 						      op.src_e * BLOCK_SIZE, _, context)). | ||||
| 				WillOnce(Return(true)); | ||||
| 			c.issue(op); | ||||
|  | ||||
| 			ASSERT_TRUE(c.nr_pending() == nr_pending + 1); | ||||
|  | ||||
| 			EXPECT_CALL(engine_, wait()). | ||||
| 				WillOnce(Return(make_wr(true, context))); | ||||
|  | ||||
| 			EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, | ||||
| 						      op.dest_b * BLOCK_SIZE, | ||||
| 						      (op.dest_b + (op.src_e - op.src_b)) * BLOCK_SIZE, _, context)). | ||||
| 				WillOnce(Return(true)); | ||||
|  | ||||
| 			EXPECT_CALL(engine_, wait()). | ||||
| 				WillOnce(Return(make_wr(true, context))); | ||||
|  | ||||
| 			auto mop = c.wait(); | ||||
| 			ASSERT_EQ(c.nr_pending(), nr_pending); | ||||
|  | ||||
| 			ASSERT_TRUE(mop->success()); | ||||
| 		} | ||||
|  | ||||
| 		unsigned const SRC_HANDLE = 10; | ||||
| 		unsigned const DEST_HANDLE = 11; | ||||
|  | ||||
| 		string src_file_; | ||||
| 		string dest_file_; | ||||
| 		StrictMock<io_engine_mock> engine_; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| TEST_F(CopierTests, empty_test) | ||||
| { | ||||
| 	auto c = make_copier(); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, successful_copy) | ||||
| { | ||||
|  	// Copy first block | ||||
| 	copy_op op1(0, 1, 0); | ||||
|  | ||||
| 	auto c = make_copier(); | ||||
| 	issue_successful_op(*c, op1, 0); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, unsuccessful_issue_read) | ||||
| { | ||||
| 	copy_op op1(0, 1, 0); | ||||
| 	auto c = make_copier(); | ||||
|  | ||||
| 	InSequence dummy; | ||||
| 	EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(false)); | ||||
| 	c->issue(op1); | ||||
|  | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	auto mop = c->wait(); | ||||
| 	ASSERT_EQ(c->nr_pending(), 0u); | ||||
| 	ASSERT_FALSE(mop->success()); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, unsuccessful_read) | ||||
| { | ||||
| 	copy_op op1(0, 1, 0); | ||||
| 	auto c = make_copier(); | ||||
|  | ||||
| 	InSequence dummy; | ||||
| 	EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(true)); | ||||
| 	c->issue(op1); | ||||
|  | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
| 	EXPECT_CALL(engine_, wait()). | ||||
| 		WillOnce(Return(make_wr(false, 0u))); | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	auto mop = c->wait(); | ||||
| 	ASSERT_EQ(c->nr_pending(), 0u); | ||||
| 	ASSERT_FALSE(mop->success()); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, unsuccessful_issue_write) | ||||
| { | ||||
| 	copy_op op1(0, 1, 0); | ||||
| 	auto c = make_copier(); | ||||
|  | ||||
| 	InSequence dummy; | ||||
| 	EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(true)); | ||||
| 	c->issue(op1); | ||||
|  | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, wait()). | ||||
| 		WillOnce(Return(make_wr(true, 0u))); | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(false)); | ||||
|  | ||||
| 	auto mop = c->wait(); | ||||
| 	ASSERT_EQ(c->nr_pending(), 0u); | ||||
| 	ASSERT_FALSE(mop->success()); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, unsuccessful_write) | ||||
| { | ||||
| 	// Copy first block | ||||
| 	copy_op op1(0, 1, 0); | ||||
|  | ||||
| 	auto c = make_copier(); | ||||
|  | ||||
| 	InSequence dummy; | ||||
| 	EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(true)); | ||||
| 	c->issue(op1); | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, wait()). | ||||
| 		WillOnce(Return(make_wr(true, 0u))); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(true)); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, wait()). | ||||
| 		WillOnce(Return(make_wr(false, 0u))); | ||||
|  | ||||
| 	auto mop = c->wait(); | ||||
| 	ASSERT_EQ(c->nr_pending(), 0u); | ||||
|  | ||||
| 	ASSERT_FALSE(mop->success()); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, copy_the_same_block_many_times) | ||||
| { | ||||
| 	auto c = make_copier(); | ||||
| 	copy_op op1(0, 1, 0); | ||||
|  | ||||
| 	for (unsigned i = 0; i < 50000; i++) | ||||
| 		issue_successful_op(*c, op1, i); | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, copy_different_blocks) | ||||
| { | ||||
| 	auto c = make_copier(); | ||||
| 	for (unsigned i = 0; i < 5000; i++) { | ||||
| 		copy_op op(i, i + 1, i); | ||||
| 		issue_successful_op(*c, op, i); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TEST_F(CopierTests, wait_can_timeout) | ||||
| { | ||||
| 	copy_op op1(0, 1, 0); | ||||
| 	auto c = make_copier(); | ||||
|  | ||||
| 	InSequence dummy; | ||||
| 	EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(true)); | ||||
| 	c->issue(op1); | ||||
|  | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	unsigned micro = 10000; | ||||
| 	EXPECT_CALL(engine_, wait(micro)). | ||||
| 		WillOnce(Return(make_wr(true, 0u))); | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, 0, BLOCK_SIZE, _, 0)). | ||||
| 		WillOnce(Return(true)); | ||||
|  | ||||
| 	EXPECT_CALL(engine_, wait(micro)). | ||||
| 		WillOnce(DoAll(SetArgReferee<0>(0u), Return(optional<wait_result>()))); | ||||
|  | ||||
| 	auto mop = c->wait(micro); | ||||
| 	ASSERT_FALSE(mop); | ||||
| 	ASSERT_EQ(c->nr_pending(), 1u); | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
							
								
								
									
										206
									
								
								unit-tests/io_engine_t.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								unit-tests/io_engine_t.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| // Copyright (C) 2016 Red Hat, Inc. All rights reserved. | ||||
| // | ||||
| // This file is part of the thin-provisioning-tools source. | ||||
| // | ||||
| // thin-provisioning-tools is free software: you can redistribute it | ||||
| // and/or modify it under the terms of the GNU General Public License | ||||
| // as published by the Free Software Foundation, either version 3 of | ||||
| // the License, or (at your option) any later version. | ||||
| // | ||||
| // thin-provisioning-tools is distributed in the hope that it will be | ||||
| // useful, but WITHOUT ANY WARRANTY; without even the implied warranty | ||||
| // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License along | ||||
| // with thin-provisioning-tools.  If not, see | ||||
| // <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| #include "gmock/gmock.h" | ||||
| #include "block-cache/mem_pool.h" | ||||
| #include "block-cache/io_engine.h" | ||||
| #include "test_utils.h" | ||||
|  | ||||
|  | ||||
| #include <fcntl.h> | ||||
|  | ||||
| using namespace boost; | ||||
| using namespace std; | ||||
| using namespace test; | ||||
| using namespace testing; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace { | ||||
| 	unsigned const MAX_IO = 64; | ||||
| 	unsigned const PAGE_SIZE = 4096; | ||||
|  | ||||
| 	class IOEngineTests : public Test { | ||||
| 	public: | ||||
| 		IOEngineTests() | ||||
| 			: pool_(64 * 512, 128 * 512, PAGE_SIZE), | ||||
| 			  src_file_("copy_src", 32), | ||||
| 			  dest_file_("copy_dest", 32), | ||||
| 			  engine_(new aio_engine(MAX_IO)) { | ||||
| 		} | ||||
|  | ||||
| 		// in sectors | ||||
| 		static uint64_t meg(unsigned n) { | ||||
| 			return 2 * 1024 * n; | ||||
| 		} | ||||
|  | ||||
| 		mempool pool_; | ||||
| 		temp_file src_file_; | ||||
| 		temp_file dest_file_; | ||||
| 		unique_ptr<io_engine> engine_; | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| TEST_F(IOEngineTests, empty_test) | ||||
| { | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, open_and_close) | ||||
| { | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	auto dest_handle = engine_->open_file(dest_file_.get_path(), io_engine::M_READ_WRITE); | ||||
| 	ASSERT_TRUE(src_handle != dest_handle); | ||||
| 	engine_->close_file(src_handle); | ||||
| 	engine_->close_file(dest_handle); | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, you_can_read_a_read_only_handle) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(src_handle, | ||||
| 				   io_engine::D_READ, | ||||
| 				   0, nr_sectors, | ||||
| 				   data, | ||||
| 				   123); | ||||
| 	ASSERT_TRUE(r); | ||||
| 	auto wr = engine_->wait(); | ||||
| 	ASSERT_TRUE(wr->first); | ||||
| 	ASSERT_TRUE(wr->second == 123); | ||||
|  | ||||
| 	engine_->close_file(src_handle); | ||||
| 	pool_.free(data); | ||||
| } | ||||
|  | ||||
|  | ||||
| TEST_F(IOEngineTests, you_cannot_write_to_a_read_only_handle) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(src_handle, | ||||
| 				   io_engine::D_WRITE, | ||||
| 				   0, nr_sectors, | ||||
| 				   data, | ||||
| 				   0); | ||||
| 	ASSERT_FALSE(r); | ||||
| 	engine_->close_file(src_handle); | ||||
| 	pool_.free(data); | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, you_can_write_to_a_read_write_handle) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(src_handle, | ||||
| 				   io_engine::D_READ, | ||||
| 				   0, nr_sectors, | ||||
| 				   data, | ||||
| 				   123); | ||||
| 	ASSERT_TRUE(r); | ||||
| 	auto wr = engine_->wait(); | ||||
| 	ASSERT_TRUE(wr->first); | ||||
| 	ASSERT_TRUE(wr->second == 123); | ||||
|  | ||||
| 	engine_->close_file(src_handle); | ||||
| 	pool_.free(data); | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, final_block_read_succeeds) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(src_handle, | ||||
| 				   io_engine::D_READ, | ||||
| 				   meg(32) - nr_sectors, meg(32), | ||||
| 				   data, | ||||
| 				   123); | ||||
| 	ASSERT_TRUE(r); | ||||
| 	auto wr = engine_->wait(); | ||||
| 	ASSERT_TRUE(wr->first); | ||||
|  | ||||
| 	engine_->close_file(src_handle); | ||||
| 	pool_.free(data); | ||||
|  | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, out_of_bounds_read_fails) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(src_handle, | ||||
| 				   io_engine::D_READ, | ||||
| 				   meg(32), meg(32) + nr_sectors, | ||||
| 				   data, | ||||
| 				   123); | ||||
| 	ASSERT_TRUE(r); | ||||
| 	auto wr = engine_->wait(); | ||||
| 	ASSERT_FALSE(wr->first); | ||||
|  | ||||
| 	engine_->close_file(src_handle); | ||||
| 	pool_.free(data); | ||||
|  | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, out_of_bounds_write_succeeds) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto handle = engine_->open_file(dest_file_.get_path(), io_engine::M_READ_WRITE); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(handle, | ||||
| 				   io_engine::D_WRITE, | ||||
| 				   meg(32), meg(32) + nr_sectors, | ||||
| 				   data, | ||||
| 				   123); | ||||
| 	ASSERT_TRUE(r); | ||||
| 	auto wr = engine_->wait(); | ||||
| 	ASSERT_TRUE(wr->first); | ||||
|  | ||||
| 	engine_->close_file(handle); | ||||
| 	pool_.free(data); | ||||
|  | ||||
| } | ||||
|  | ||||
| TEST_F(IOEngineTests, succeed_with_timeout) | ||||
| { | ||||
| 	unsigned nr_sectors = 8; | ||||
| 	auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY); | ||||
| 	void *data = pool_.alloc(); | ||||
| 	bool r = engine_->issue_io(src_handle, | ||||
| 				   io_engine::D_READ, | ||||
| 				   0, nr_sectors, | ||||
| 				   data, | ||||
| 				   123); | ||||
| 	ASSERT_TRUE(r); | ||||
| 	unsigned micro = 10; | ||||
| 	auto wr = engine_->wait(micro); | ||||
| 	ASSERT_TRUE(wr->first); | ||||
| 	ASSERT_TRUE(wr->second == 123); | ||||
|  | ||||
| 	engine_->close_file(src_handle); | ||||
| 	pool_.free(data); | ||||
| } | ||||
|  | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
							
								
								
									
										112
									
								
								unit-tests/mem_pool_t.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								unit-tests/mem_pool_t.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| // Copyright (C) 2016 Red Hat, Inc. All rights reserved. | ||||
| // | ||||
| // This file is part of the thin-provisioning-tools source. | ||||
| // | ||||
| // thin-provisioning-tools is free software: you can redistribute it | ||||
| // and/or modify it under the terms of the GNU General Public License | ||||
| // as published by the Free Software Foundation, either version 3 of | ||||
| // the License, or (at your option) any later version. | ||||
| // | ||||
| // thin-provisioning-tools is distributed in the hope that it will be | ||||
| // useful, but WITHOUT ANY WARRANTY; without even the implied warranty | ||||
| // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License along | ||||
| // with thin-provisioning-tools.  If not, see | ||||
| // <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| #include "gmock/gmock.h" | ||||
| #include "block-cache/mem_pool.h" | ||||
| #include "test_utils.h" | ||||
|  | ||||
| #include <fcntl.h> | ||||
|  | ||||
| using namespace boost; | ||||
| using namespace std; | ||||
| using namespace test; | ||||
| using namespace testing; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| namespace { | ||||
| 	class MempoolTests : public Test { | ||||
| 	public: | ||||
| 		bool aligned(void *data, size_t alignment) { | ||||
| 			return (reinterpret_cast<size_t>(data) % alignment) == 0; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
|  | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| TEST_F(MempoolTests, empty_test) | ||||
| { | ||||
| } | ||||
|  | ||||
| TEST_F(MempoolTests, create_destroy_cycle) | ||||
| { | ||||
| 	for (size_t bs = 64; bs <= 512; bs *= 2) { | ||||
| 		mempool mp(bs, 4 * 1024 * 1024, bs); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TEST_F(MempoolTests, alignments_observed) | ||||
| { | ||||
| 	for (size_t bs = 64; bs <= 512; bs *= 2) { | ||||
| 		mempool mp(bs, 512 * 1024, bs); | ||||
|  | ||||
| 		for (unsigned i = 0; i < 100; i++) { | ||||
| 			auto md = mp.alloc(); | ||||
|  | ||||
| 			if (!md) | ||||
| 				throw runtime_error("couldn't alloc"); | ||||
|  | ||||
| 			ASSERT_TRUE(aligned(md, bs)); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TEST_F(MempoolTests, alloc_free_cycle) | ||||
| { | ||||
| 	mempool mp(512, 512 * 1024, 512); | ||||
|  | ||||
| 	for (unsigned i = 0; i < 10000; i++) { | ||||
| 		auto md = mp.alloc(); | ||||
| 		mp.free(md); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TEST_F(MempoolTests, exhaust_pool) | ||||
| { | ||||
| 	mempool mp(512, 100 * 512, 512); | ||||
|  | ||||
| 	for (unsigned i = 0; i < 100; i++) { | ||||
| 		auto md = mp.alloc(); | ||||
| 		ASSERT_NE(md, nullptr); | ||||
| 	} | ||||
|  | ||||
| 	auto md = mp.alloc(); | ||||
| 	ASSERT_EQ(md, nullptr); | ||||
| } | ||||
|  | ||||
| // Use valgrind | ||||
| TEST_F(MempoolTests, data_can_be_written) | ||||
| { | ||||
| 	mempool mp(512, 100 * 512, 512); | ||||
|  | ||||
| 	for (unsigned i = 0; i < 100; i++) { | ||||
| 		auto md = mp.alloc(); | ||||
| 		ASSERT_NE(md, nullptr); | ||||
|  | ||||
| 		memset(md, 0, 512); | ||||
| 	} | ||||
|  | ||||
| 	auto md = mp.alloc(); | ||||
| 	ASSERT_EQ(md, nullptr); | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
| @@ -3,6 +3,8 @@ | ||||
| #include "persistent-data/space-maps/core.h" | ||||
|  | ||||
| using namespace persistent_data; | ||||
| using namespace std; | ||||
| using namespace test; | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| @@ -21,3 +23,35 @@ test::open_temporary_tm(block_manager<>::ptr bm) | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|  | ||||
| temp_file::temp_file(string const &name_base, unsigned meg_size) | ||||
| 	: path_(gen_path(name_base)) | ||||
| { | ||||
| 	int fd = ::open(path_.c_str(), O_CREAT | O_RDWR, 0666); | ||||
| 	if (fd < 0) | ||||
| 		throw runtime_error("couldn't open file"); | ||||
|  | ||||
| 	if (::fallocate(fd, 0, 0, 1024 * 1024 * meg_size)) | ||||
| 		throw runtime_error("couldn't fallocate"); | ||||
|  | ||||
| 	::close(fd); | ||||
| } | ||||
|  | ||||
| temp_file::~temp_file() | ||||
| { | ||||
| //	::unlink(path_.c_str()); | ||||
| } | ||||
|  | ||||
| string const & | ||||
| temp_file::get_path() const | ||||
| { | ||||
| 	return path_; | ||||
| } | ||||
|  | ||||
| string | ||||
| temp_file::gen_path(string const &base) | ||||
| { | ||||
| 	return string("./") + base + string(".tmp"); | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|   | ||||
| @@ -114,6 +114,18 @@ namespace test { | ||||
| 		std::unique_ptr<with_directory> dir_; | ||||
| 	}; | ||||
|  | ||||
| 	class temp_file { | ||||
| 	public: | ||||
| 		temp_file(std::string const &name_base, unsigned meg_size); | ||||
| 		~temp_file(); | ||||
| 		std::string const &get_path() const; | ||||
|  | ||||
| 	private: | ||||
| 		static string gen_path(string const &base); | ||||
|  | ||||
| 		string path_; | ||||
| 	}; | ||||
|  | ||||
| 	//-------------------------------- | ||||
|  | ||||
| 	template <typename T> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user