diff --git a/.gitignore b/.gitignore index 55164d5..9af018d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ cache_check cache_dump cache_restore cache_repair +cache_metadata_size *.metadata bad-metadata diff --git a/Makefile.in b/Makefile.in index 73326b1..0a93f03 100644 --- a/Makefile.in +++ b/Makefile.in @@ -24,6 +24,7 @@ PROGRAMS=\ cache_dump \ cache_restore \ cache_repair \ + cache_metadata_size \ \ thin_check \ thin_dump \ @@ -280,6 +281,10 @@ cache_restore: $(CACHE_RESTORE_OBJECTS) caching/cache_restore.o @echo " [LD] $@" $(V) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $+ $(LIBS) $(LIBEXPAT) +cache_metadata_size: caching/cache_metadata_size.o + @echo " [LD] $@" + $(V) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $+ $(LIBS) + DEPEND_FILES=\ $(subst .cc,.d,$(SOURCE)) \ $(subst .cc,.d,$(TEST_SOURCE)) \ diff --git a/caching/cache_metadata_size.cc b/caching/cache_metadata_size.cc new file mode 100644 index 0000000..47b4322 --- /dev/null +++ b/caching/cache_metadata_size.cc @@ -0,0 +1,156 @@ +#include "version.h" + +#include +#include +#include +#include +#include +#include + +using namespace boost; +using namespace std; + +//---------------------------------------------------------------- + +namespace { + struct flags { + flags() + : max_hint_width(4) { + } + + optional device_size; + optional block_size; + optional nr_blocks; + uint32_t max_hint_width; + }; + + void usage(ostream &out, string const &cmd) { + out << "Usage: " << cmd << " [options]" << endl + << "Options:" << endl + << " {-h|--help}" << endl + << " {-V|--version}" << endl + << " {--block-size }" << endl + << " {--device-size }" << endl + << " {--nr-blocks }" << endl << endl + << "These all relate to the size of the fast device (eg, SSD), rather" << endl + << "than the whole cached device." << endl; + } + + enum parse_result { + FINISH, + CONTINUE + }; + + parse_result parse_command_line(string const &prog_name, int argc, char **argv, flags &fs) { + + int c; + char const short_opts[] = "hV"; + option const long_opts[] = { + { "block-size", required_argument, NULL, 0 }, + { "device-size", required_argument, NULL, 1 }, + { "nr-blocks", required_argument, NULL, 2 }, + { "max-hint-width", required_argument, NULL, 3 }, + { "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.block_size = lexical_cast(optarg); + break; + + case 1: + fs.device_size = lexical_cast(optarg); + break; + + case 2: + fs.nr_blocks = lexical_cast(optarg); + break; + + case 3: + fs.max_hint_width = lexical_cast(optarg); + + case 'h': + usage(cout, prog_name); + return FINISH; + break; + + case 'V': + cout << THIN_PROVISIONING_TOOLS_VERSION << endl; + return FINISH; + break; + + default: + usage(cerr, prog_name); + throw runtime_error("Invalid command line"); + break; + } + } + + return CONTINUE; + } + + void expand_flags(flags &fs) { + if (!fs.device_size && !fs.nr_blocks) + throw runtime_error("Please specify either --device-size and --block-size, or --nr-blocks."); + + if (fs.device_size && !fs.block_size) + throw runtime_error("If you specify --device-size you must also give --block-size."); + + if (fs.block_size && !fs.device_size) + throw runtime_error("If you specify --block-size you must also give --device-size."); + + if (fs.device_size && fs.block_size) { + uint64_t nr_blocks = *fs.device_size / *fs.block_size; + if (fs.nr_blocks) { + if (nr_blocks != *fs.nr_blocks) + throw runtime_error( + "Contradictory arguments given, --nr-blocks doesn't match the --device-size and --block-size."); + } else + fs.nr_blocks = nr_blocks; + } + } + + uint64_t meg(uint64_t n) { + return n * 2048; + } + + uint64_t calc_size(flags const &fs) { + uint64_t const SECTOR_SIZE = 512; + uint64_t const TRANSACTION_OVERHEAD = meg(4); + uint64_t const BYTES_PER_BLOCK = 16; + uint64_t const HINT_OVERHEAD_PER_BLOCK = 8; + + uint64_t mapping_size = (*fs.nr_blocks * BYTES_PER_BLOCK) / SECTOR_SIZE; + uint64_t hint_size = (*fs.nr_blocks * (fs.max_hint_width + HINT_OVERHEAD_PER_BLOCK)) / SECTOR_SIZE; + return TRANSACTION_OVERHEAD + mapping_size + hint_size; + } +} + +int main(int argc, char **argv) +{ + flags fs; + + try { + switch (parse_command_line(argv[0], argc, argv, fs)) { + case FINISH: + return 0; + + case CONTINUE: + break; + } + + expand_flags(fs); + cout << calc_size(fs) << " sectors" << endl; + + } catch (std::exception const &e) { + cerr << e.what(); + return 1; + } + + return 0; +} + +//---------------------------------------------------------------- diff --git a/features/cache_metadata_size.feature b/features/cache_metadata_size.feature new file mode 100644 index 0000000..9b33e34 --- /dev/null +++ b/features/cache_metadata_size.feature @@ -0,0 +1,102 @@ +Feature: cache_metadata_size + Scenario: print version (-V flag) + When I run cache_metadata_size with -V + Then it should pass with version + + Scenario: print version (--version flag) + When I run cache_metadata_size with --version + Then it should pass with version + + Scenario: print help (-h) + When I run cache_metadata_size with -h + Then it should pass + And the output should contain exactly: + + """ + Usage: cache_metadata_size [options] + Options: + {-h|--help} + {-V|--version} + {--block-size } + {--device-size } + {--nr-blocks } + + These all relate to the size of the fast device (eg, SSD), rather + than the whole cached device. + + """ + + Scenario: print help (--help) + When I run cache_metadata_size with -h + Then it should pass + And the output should contain exactly: + + """ + Usage: cache_metadata_size [options] + Options: + {-h|--help} + {-V|--version} + {--block-size } + {--device-size } + {--nr-blocks } + + These all relate to the size of the fast device (eg, SSD), rather + than the whole cached device. + + """ + + Scenario: No arguments specified causes fail + When I run cache_metadata_size + Then it should fail with: + """ + Please specify either --device-size and --block-size, or --nr-blocks. + """ + + Scenario: Just --device-size causes fail + When I run cache_metadata_size with --device-size 102400 + Then it should fail with: + """ + If you specify --device-size you must also give --block-size. + """ + + Scenario: Just --block-size causes fail + When I run cache_metadata_size with --block-size 64 + Then it should fail with: + """ + Please specify either --device-size and --block-size, or --nr-blocks. + """ + + Scenario: Contradictory info causes fail + When I run cache_metadata_size with --device-size 102400 --block-size 1000 --nr-blocks 6 + Then it should fail with: + """ + Contradictory arguments given, --nr-blocks doesn't match the --device-size and --block-size. + """ + + Scenario: All args agreeing succeeds + When I run cache_metadata_size with --device-size 102400 --block-size 100 --nr-blocks 1024 + Then it should pass with: + """ + 8248 sectors + """ + + Scenario: Just --nr-blocks succeeds + When I run cache_metadata_size with --nr-blocks 1024 + Then it should pass with: + """ + 8248 sectors + """ + + Scenario: Just --device-size and --block-size succeeds + When I run cache_metadata_size with --device-size 102400 --block-size 100 + Then it should pass with: + """ + 8248 sectors + """ + + Scenario: A big configuration passes + When I run cache_metadata_size with --nr-blocks 67108864 + Then it should pass with: + """ + 3678208 + """ \ No newline at end of file diff --git a/features/step_definitions/cache_steps.rb b/features/step_definitions/cache_steps.rb index 58217f4..2f03fa7 100644 --- a/features/step_definitions/cache_steps.rb +++ b/features/step_definitions/cache_steps.rb @@ -69,6 +69,14 @@ When(/^I run cache_dump with (.*?)$/) do |opts| run_simple("cache_dump #{opts}", false) end +When(/^I run cache_metadata_size with (.*?)$/) do |opts| + run_simple("cache_metadata_size #{opts}", false) +end + +When(/^I run cache_metadata_size$/) do + run_simple("cache_metadata_size", false) +end + Given(/^valid cache metadata$/) do in_current_dir do system("cache_xml create --nr-cache-blocks uniform[1000..5000] --nr-mappings uniform[500..1000] > #{xml_file}")