--- lib/activate/dev_manager.c | 5 lib/format_text/export.c | 30 + lib/format_text/import_vsn1.c | 43 + lib/metadata/lv.h | 1 lib/metadata/lv_manip.c | 1 libdm/libdevmapper.h | 11 libdm/libdm-deptree.c | 70 ++ tools/Makefile | 3 tools/Makefile.in | 1 tools/args.h | 17 tools/commands.h | 20 tools/lvstats.c | 1243 ++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 1442 insertions(+), 3 deletions(-) Index: lvm2-copy/tools/Makefile.in =================================================================== --- lvm2-copy.orig/tools/Makefile.in 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/tools/Makefile.in 2013-10-20 15:40:35.000000000 +0200 @@ -32,6 +32,7 @@ SOURCES =\ lvrename.c \ lvresize.c \ lvscan.c \ + lvstats.c \ polldaemon.c \ pvchange.c \ pvck.c \ Index: lvm2-copy/tools/lvstats.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ lvm2-copy/tools/lvstats.c 2013-10-28 01:48:12.000000000 +0100 @@ -0,0 +1,1243 @@ +#include "tools.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * stats + * create: + * -c [ ] [ -g | -granularity-segments ] [ -s starting_sector ] [ -e ending_sector ] + * delete: + * -d [ ] [ -g | -granularity-segments ] [ -s starting_sector ] [ -e ending_sector ] + * query: + * -q [ ] [ --clear ] [ --raw ] [ --bi ] + * list: + * -l [ ] + * clear: + * --clear [ ] + * auto subdivide: + * [ ] -a [ --auto-subdivide-* ] + * delete auto subdivided ranges: + * [ ] -ad + */ + +#define SYS_BLOCK "/sys/block" +#define DEV "/dev/" + +#define ERROR_STATUS 1 +#define ERROR_PARAMS 4 + +#define GRANULARITY_DEFAULT (65536 * 1024 / 512) +#define GRANULARITY_DEFAULT_MIN_STEPS 16 +#define GRANULARITY_SUBDIVIDE 16 + +#define AUTO_SUBDIVIDE_TIME 600 +#define AUTO_SUBDIVIDE_GRANULARITY 256 +#define AUTO_SUBDIVIDE_DEPTH 1 +#define AUTO_SUBDIVIDE_NUMBER 3 + +#define READ_BATCH 50000 + +static const char *device_name; +static struct logical_volume *logical_volume = NULL; + +#define MODE_CHECK 0 +#define MODE_CREATE 1 +#define MODE_DELETE 2 +#define MODE_QUERY 3 +#define MODE_LIST 4 +static char mode = MODE_CHECK; +static bool update_metadata = false; +static bool lv_specified = false; +static bool dm = false; +static bool clear = false; +static bool raw = false; +static bool bi = false; +static bool nomerge = false; +static bool seek = false; +static uint64_t granularity = 0; +static unsigned granularity_n = 0; +static bool have_start_sector = false; +static uint64_t start_sector = 0; +static bool have_end_sector = false; +static uint64_t end_sector = 0; +static bool auto_subdivide = false; +static unsigned auto_subdivide_granularity = 0; +static unsigned auto_subdivide_time = 0; +static unsigned auto_subdivide_depth = 0; +static unsigned auto_subdivide_number = 0; + +struct collected_data { + size_t idx; + uint64_t ios; + uint64_t reads; + uint64_t reads_merged; + uint64_t read_sectors; + uint64_t read_msec; + uint64_t writes; + uint64_t writes_merged; + uint64_t write_sectors; + uint64_t write_msec; + uint64_t io_in_progress; + uint64_t io_msec; + uint64_t weighted_io_msec; + uint64_t reading_msec; + uint64_t writing_msec; +}; + +struct range { + int id; + uint64_t start; + uint64_t len; + uint64_t step; + size_t steps; + struct collected_data *data; + bool auto_subdivided; + bool auto_subdivide; + unsigned auto_subdivide_granularity; + unsigned auto_subdivide_time; + unsigned auto_subdivide_depth; + unsigned auto_subdivide_number; + uint64_t auto_subdivide_limit; +}; + +static int retval = ECMD_PROCESSED; + +/* The error handler */ + +static jmp_buf *jmp; + +static struct dm_pool *command_pool = NULL; + +/* These three variables must be freed by the error handler */ + +static struct dm_pool *device_pool = NULL; +static struct dm_task *dmt = NULL; +static DIR *dir = NULL; + +static void lvstats_free_pool(void); + +static void lvstats_error_cleanup(void) +{ + if (dmt) + dm_task_destroy(dmt), dmt = NULL; + if (device_pool) + lvstats_free_pool(), device_pool = NULL; +} + +/* Report an error and do longjmp to cleanup code */ + +__attribute__((__format__(__printf__, 1, 2))) static void lvstats_error(const char *msg, ...) +{ + va_list va; + char *str; + va_start(va, msg); + if (dm_vasprintf(&str, msg, va) < 0) { + va_end(va); + log_error("dm_vasprintf failed (%s)", msg); + goto lj; + } + va_end(va); + log_error("%s", str); + dm_free(str); +lj: + lvstats_error_cleanup(); + longjmp(*jmp, 1); +} + + +/* A wrapper for malloc that never fails */ + +static void *lvstats_malloc(size_t s) +{ + void *p = dm_pool_alloc(device_pool ? device_pool : command_pool, s); + if (!p) + lvstats_error("Out of memory (requested size %lu)", (unsigned long)s); + /*printf("alloc(%lu): %p\n", (unsigned long)s, p);*/ + return p; +} + +/* + * There is no free All the data are freed when the pool is freed. + * This function is here just for the case that we change to malloc/free + * in the future. + */ + +/* A wrapper for realloc that never fails */ + +static void *lvstats_realloc(void *p, size_t old_size, size_t new_size) +{ + void *n = lvstats_malloc(new_size); + memcpy(n, p, old_size < new_size ? old_size : new_size); + return n; +} + +/* A wrapper for strdup that never fails */ + +static char *lvstats_strdup(const char *s) +{ + char *a = dm_pool_strdup(device_pool ? device_pool : command_pool, s); + if (!a) + lvstats_error("Out of memory (requested size %lu)", (unsigned long)(strlen(s) + 1)); + return a; +} + +/* A wrapper for strndup that never fails */ + +static char *lvstats_strndup(const char *s, size_t l) +{ + char *a = dm_pool_strndup(device_pool ? device_pool : command_pool, s, l); + if (!a) + lvstats_error("Out of memory (requested size %lu)", (unsigned long)(l + 1)); + return a; +} + +/* Convert libc-allocated string to a dm_pool-allocated string */ + +static char *lvstats_malloc_to_pool(char *s, bool dm_fr) +{ + char *a = lvstats_strdup(s); + if (dm_fr) dm_free(s); + else free(s); + return a; +} + +/* Execute the given function for each line in the "msg" string */ + +static void break_into_lines(char *msg, void (*function)(const char *)) +{ + while (*msg) { + char *eol = strchr(msg, '\n'); + if (!eol) + lvstats_error("%s: unterminated line: \"%s\"", device_name, msg); + *eol = 0; + function(msg); + msg = eol + 1; + } +} + +/* Convert the file name "/dev/dm-1234" to real device mapper name */ + +static char *get_dm_name(const char *dev) +{ + char *name; + if (!(dmt = dm_task_create(DM_DEVICE_INFO))) + lvstats_error("%s: dm_task_create(DM_DEVICE_TARGET_MSG) failed", dev); + if (!dm_task_set_name(dmt, dev)) + lvstats_error("%s: dm_task_set_name failed", dev); + if (!dm_task_run(dmt)) + lvstats_error("%s: DM_DEVICE_INFO ioctl failed", dev); + name = lvstats_strdup(dm_task_get_name(dmt)); + dm_task_destroy(dmt); + dmt = NULL; + return name; +} + +/* Execute the given message on a device specified by "device_name" and return resulting string */ + +static char *do_message(const char *msg) +{ + const char *result; + char *result_ret; + + if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) + lvstats_error("%s: dm_task_create(DM_DEVICE_TARGET_MSG) failed", device_name); + if (!dm_task_set_name(dmt, device_name)) + lvstats_error("%s: dm_task_set_name failed", device_name); + if (!dm_task_set_sector(dmt, 0)) + lvstats_error("%s: dm_task_set_sector failed", device_name); + + if (!dm_task_set_message(dmt, msg)) + lvstats_error("%s: dm_task_set_message(%s) failed", device_name, msg); + + errno = 0; + if (!dm_task_run(dmt)) { + if (errno == ENOMEM) + lvstats_error("%s: out of kernel memory", device_name); + lvstats_error("%s: dm_task_run(%s) failed: %s", device_name, msg, strerror(errno)); + } + + result = dm_task_get_message_response(dmt); + if (!result) { + result_ret = NULL; + } else { + result_ret = lvstats_strdup(result); + } + + dm_task_destroy(dmt); + dmt = NULL; + + return result_ret; +} + +/* do_message_args executes a message with printf-like argument list */ + +__attribute__((__format__(__printf__, 1, 2))) static char *do_message_args(const char *msg, ...) +{ + va_list va; + char *str; + va_start(va, msg); + if (dm_vasprintf(&str, msg, va) < 0) { + va_end(va); + lvstats_error("dm_vasprintf failed (%s)", msg); + } + va_end(va); + str = lvstats_malloc_to_pool(str, true); + return do_message(str); +} + +/* Make a string for auto-subdivide auxiliary argument */ + +static char *make_auto_subdivide_string_from_args(unsigned gran, unsigned tim, unsigned depth, unsigned number) +{ + char *auto_subdivide_string; + if (dm_asprintf(&auto_subdivide_string, "auto-subdivide:%u,%u,%u,%u,%"PRIu64, + gran, + tim, + depth, + number, + (uint64_t)time(NULL) + tim + ) < 0) + lvstats_error("dm_asprintf failed"); + auto_subdivide_string = lvstats_malloc_to_pool(auto_subdivide_string, true); + return auto_subdivide_string; +} + +/* Make a string for auto-subdivide with commandline-supplied arguments */ + +static char *make_auto_subdivide_string(void) +{ + return make_auto_subdivide_string_from_args( + auto_subdivide_granularity, + auto_subdivide_time, + auto_subdivide_depth, + auto_subdivide_number); +} + +/* Find an auxiliary argument with a given name. They are separated with '/' */ + +static char *find_aux_arg(const char *aux, const char *string) +{ + size_t string_len = strlen(string); + if (!aux) + return NULL; +try_next: + if (!strncmp(aux, string, string_len)) { + aux += string_len; + if (!*aux || *aux == '/') + return lvstats_strdup(""); + if (*aux == ':') { + aux++; + return lvstats_strndup(aux, strcspn(aux, "/")); + } + } + aux = strchr(aux, '/'); + if (!aux) + return NULL; + aux++; + goto try_next; +} + +static struct range *ranges; +static size_t n_ranges; + +/* + * get_range parses one line from the output of @stats_list message and + * adds it to the "ranges" array. + */ + +static void get_range(const char *line) +{ + char *aux = NULL; + char *autosub; + uint64_t steps; + + if (!((n_ranges + 1) & n_ranges)) + ranges = lvstats_realloc(ranges, n_ranges * sizeof(struct range), (n_ranges * 2 + 1) * sizeof(struct range)); + + if (sscanf(line, "%d: %"SCNu64"+%"SCNu64" %"SCNu64" "LVSTATS_NAME" %ms", + &ranges[n_ranges].id, + &ranges[n_ranges].start, + &ranges[n_ranges].len, + &ranges[n_ranges].step, + &aux) + < 5 || + !ranges[n_ranges].step) +invalid_line: + lvstats_error("%s: invalid status line \"%s\"", device_name, line); + + aux = lvstats_malloc_to_pool(aux, false); + + ranges[n_ranges].auto_subdivided = false; + if ((autosub = find_aux_arg(aux, "auto-subdivided"))) { + ranges[n_ranges].auto_subdivided = true; + } + + ranges[n_ranges].auto_subdivide = false; + if ((autosub = find_aux_arg(aux, "auto-subdivide"))) { + if (sscanf(autosub, "%d,%d,%d,%d,%"SCNu64, + &ranges[n_ranges].auto_subdivide_granularity, + &ranges[n_ranges].auto_subdivide_time, + &ranges[n_ranges].auto_subdivide_depth, + &ranges[n_ranges].auto_subdivide_number, + &ranges[n_ranges].auto_subdivide_limit) < 5) + goto invalid_line; + if (ranges[n_ranges].auto_subdivide_granularity <= 0 || + ranges[n_ranges].auto_subdivide_depth <= 0 || + ranges[n_ranges].auto_subdivide_number <= 0) + goto invalid_line; + ranges[n_ranges].auto_subdivide = true; + } + + steps = (ranges[n_ranges].len + ranges[n_ranges].step - 1) / ranges[n_ranges].step; + if ((size_t)steps * sizeof(struct collected_data) / sizeof(struct collected_data) != steps) + lvstats_error("%s: integer overflow in steps (%"PRIu64")", device_name, steps); + + ranges[n_ranges].steps = steps; + ranges[n_ranges].data = NULL; + + n_ranges++; + if (!(n_ranges + 1)) + lvstats_error("%s: n_ranges overflow", device_name); +} + +/* + * Send a command to delete the given range. + * If "autosub_only" is true, only automatically-subdivided ranges are + * delete. + */ + +static bool is_range_selected(const struct range *r) +{ + if (have_start_sector) { + if (r->start != start_sector) + return false; + } + if (have_end_sector) { + if (r->start + r->len != end_sector) + return false; + } + if (granularity) { + if (r->step != granularity) + return false; + } + if (granularity_n) { + if (r->steps != granularity_n) + return false; + } + return true; +} + + +/* + * Perform the "delete" operation on the current device. + * If "-s sector" is specified, a specified range is deleted, + * otherwise all ranges are deleted. + * + * If "autosub_only" is specified, only automatically-subdivided ranges + * are deleted. + */ + +static void del(bool autosub_only) +{ + size_t x; + bool did_something = false; + for (x = n_ranges; x > 0; ) { + const struct range *r; + x--; + r = &ranges[x]; + if (is_range_selected(r)) { + if (autosub_only) { + if (!r->auto_subdivided) { + if (r->auto_subdivide) { + do_message_args("@stats_set_aux %d -", r->id); + did_something = true; + } + continue; + } + } + do_message_args("@stats_delete %d", r->id); + did_something = true; + } + } + if (!did_something) { + lvstats_error("%s: delete: no range matched selection criteria", device_name); + } +} + +/* Create the new range to be monitored */ + +static void create(void) +{ + char *auto_subdivide_string = NULL; + char *range_string; + uint64_t granularity_default = GRANULARITY_DEFAULT; + if (!have_start_sector) { + if (n_ranges) { + lvstats_error("%s: statistics already exist", device_name); + return; + } + range_string = (char *)"-"; + } else { + uint64_t end, step; + size_t x; + if (have_end_sector) { + end = end_sector; + } else { + end = (size_t)-1; + for (x = 0; x < n_ranges; x++) { + if (start_sector >= ranges[x].start && start_sector < ranges[x].start + ranges[x].len) { + uint64_t offset, e; + offset = start_sector - ranges[x].start; + if (offset % ranges[x].step) + continue; + e = start_sector + ranges[x].step; + if (e > ranges[x].start + ranges[x].len) + e = ranges[x].start + ranges[x].len; + if (end == (size_t)-1 || e < end) + end = e; + } + } + if (end == (size_t)-1) { + lvstats_error("%s: no range at specified boundary", device_name); + return; + } + } + if (dm_asprintf(&range_string, "%"PRIu64"+%"PRIu64"", + start_sector, end - start_sector) < 0) + lvstats_error("dm_asprintf failed"); + range_string = lvstats_malloc_to_pool(range_string, true); + while (granularity_default > 1 && (end - start_sector) / GRANULARITY_DEFAULT_MIN_STEPS < granularity_default) + granularity_default >>= 1; + if (granularity_n > 0) { + step = (end - start_sector + (granularity_n - 1)) / granularity_n; + if (!step) + step = 1; + } else if (granularity > 0) { + step = granularity; + } else { + step = granularity_default; + } + for (x = 0; x < n_ranges; x++) { + if (ranges[x].start == start_sector && + ranges[x].len == end - start_sector && + ranges[x].step == step) { + lvstats_error("%s: the range with identical parameters already exists", device_name); + return; + } + } + } + if (auto_subdivide) { + auto_subdivide_string = make_auto_subdivide_string(); + } + do_message_args("@stats_create %s %s%"PRIu64" %s%s%s", + range_string, + granularity_n > 0 ? "/" : "", + granularity_n > 0 ? granularity_n : granularity > 0 ? granularity : granularity_default, + LVSTATS_NAME, + auto_subdivide_string ? " " : "", + auto_subdivide_string ? auto_subdivide_string : "" + ); +} + +static struct range *q_r; +static size_t q_idx, q_remaining; + +/* + * This function parses one line returned by "@stats_print" message. + * and places it into the "q_r" array. + * q_idx is the index where the returned line should be placed, + * q_remaining is the number of remaining entries in the array. + */ + +static void get_stats(const char *line) +{ + uint64_t start, len; + uint64_t reads, reads_merged, read_sectors, read_msec; + uint64_t writes, writes_merged, write_sectors, write_msec; + uint64_t io_in_progress, io_msec, weighted_io_msec; + uint64_t reading_msec, writing_msec; + char dummy; + + if (!q_remaining) + lvstats_error("%s: too many lines returned", device_name); + if (sscanf(line, "%"SCNu64"+%"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64"%c", + &start, + &len, + &reads, + &reads_merged, + &read_sectors, + &read_msec, + &writes, + &writes_merged, + &write_sectors, + &write_msec, + &io_in_progress, + &io_msec, + &weighted_io_msec, + &reading_msec, + &writing_msec, + &dummy) != 15) + lvstats_error("%s: invalid line returned: \"%s\"", device_name, line); + + q_r->data[q_idx].idx = q_idx; + q_r->data[q_idx].ios = reads + writes; + q_r->data[q_idx].reads = reads; + q_r->data[q_idx].reads_merged = reads_merged; + q_r->data[q_idx].read_sectors = read_sectors; + q_r->data[q_idx].read_msec = read_msec; + q_r->data[q_idx].writes = writes; + q_r->data[q_idx].writes_merged = writes_merged; + q_r->data[q_idx].write_sectors = write_sectors; + q_r->data[q_idx].write_msec = write_msec; + q_r->data[q_idx].io_in_progress = io_in_progress; + q_r->data[q_idx].io_msec = io_msec; + q_r->data[q_idx].weighted_io_msec = weighted_io_msec; + q_r->data[q_idx].reading_msec = reading_msec; + q_r->data[q_idx].writing_msec = writing_msec; + + q_idx++; + q_remaining--; +} + +/* + * This function sends the device mapper message to query the data for the + * specified range and fills the array q->data. + */ + +static void query_range(struct range *r) +{ + q_r = r; + q_r->data = lvstats_malloc(q_r->steps * sizeof(struct collected_data)); + for (q_idx = 0; q_idx < q_r->steps; ) { + char *q; + q_remaining = READ_BATCH; + if (q_idx + q_remaining < q_idx || q_idx + q_remaining > q_r->steps) + q_remaining = q_r->steps - q_idx; + q = do_message_args("@stats_print%s %d %zu %zu", clear ? "_clear" : "", q_r->id, q_idx, q_remaining); + if (!q) + lvstats_error("%s: no data returned", device_name); + + break_into_lines(q, get_stats); + + if (q_remaining) + lvstats_error("%s: insufficient number of lines returned: %zu, %zu", device_name, q_idx, q_remaining); + } +} + +/* Return the number of ios according to the "--nomerge" argument */ + +static uint64_t num_ios(uint64_t ios, uint64_t ios_merged) +{ + if (nomerge) return ios; + if (ios < ios_merged) return 0; + return ios - ios_merged; +} + +/* Convert sectors to megabytes or mebibites */ + +static double transferred(uint64_t sectors) +{ + return (double)sectors * (bi ? 512. / 1048576. : 512. / 1000000.); +} + +/* Calculate throughput from the number of sectors and milliseconds */ + +static double throughput(uint64_t sectors, uint64_t msec) +{ + if (!msec) return 0; + return (double)sectors / (double)msec * (bi ? 512. / 1000. * 1000000. / 1048576. : 512. / 1000.); +} + +/* Calculate average request latency or seek time */ + +static double latency(uint64_t requests, uint64_t msec, uint64_t busy_msec) +{ + if (!requests) return 0; + if (seek) msec = busy_msec; + return (double)msec / (double)requests; +} + +/* Perform the "query" operation - ask the kernel for data and display them */ + +static void query(void) +{ + /* !!! FIXME: process it range-by-range */ + bool first = true; + size_t x, y; + for (x = 0; x < n_ranges; x++) { + if (!is_range_selected(&ranges[x])) + continue; + query_range(&ranges[x]); + if (first) { + printf("%s:\n", device_name); + first = false; + } + printf(" %"PRIu64"+%"PRIu64" / %"PRIu64"\n", + ranges[x].start, + ranges[x].len, + ranges[x].step); + for (y = 0; y < ranges[x].steps; y++) { + struct collected_data *c = &ranges[x].data[y]; + uint64_t start = ranges[x].start + y * ranges[x].step; + uint64_t len = ranges[x].step; + if (start + len > ranges[x].start + ranges[x].len) + len = ranges[x].start + ranges[x].len - start; + printf(" %"PRIu64"+%"PRIu64":", + start, + len + ); + if (!raw) { + printf(" %"PRIu64" %.1fM%sB %.3fM%sB/s %.3fms %"PRIu64" %.1fM%sB %.3fM%sB/s %.3fms", + num_ios(c->reads, c->reads_merged), + transferred(c->read_sectors), + bi ? "i" : "", + throughput(c->read_sectors, c->reading_msec), + bi ? "i" : "", + latency(num_ios(c->reads, c->reads_merged), c->read_msec, c->reading_msec), + num_ios(c->writes, c->writes_merged), + transferred(c->write_sectors), + bi ? "i" : "", + throughput(c->write_sectors, c->writing_msec), + bi ? "i" : "", + latency(num_ios(c->writes, c->writes_merged), c->write_msec, c->writing_msec) + ); + } else { + printf(" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64"", + c->reads, + c->reads_merged, + c->read_sectors, + c->read_msec, + c->writes, + c->writes_merged, + c->write_sectors, + c->write_msec, + c->io_in_progress, + c->io_msec, + c->weighted_io_msec, + c->reading_msec, + c->writing_msec + ); + } + printf("\n"); + } + } +} + +/* List all ranges that are being monitored */ + +static void list(void) +{ + size_t x; + if (!n_ranges) + return; + printf("%s:\n", device_name); + for (x = 0; x < n_ranges; x++) { + if (!is_range_selected(&ranges[x])) + continue; + printf("%"PRIu64"+%"PRIu64" / %"PRIu64"", + ranges[x].start, + ranges[x].len, + ranges[x].step); + if (ranges[x].auto_subdivided) + printf(" auto-subdivided"); + if (ranges[x].auto_subdivide) { + uint64_t now = time(NULL); + printf(" auto-subdivide("); + if (ranges[x].auto_subdivide_limit > now) + printf("time=%"PRIu64, ranges[x].auto_subdivide_limit - now); + else + printf("expired"); + if (ranges[x].auto_subdivide_depth > 1) + printf(",depth=%d", ranges[x].auto_subdivide_depth); + printf(")"); + } + putchar('\n'); + } +} + +/* Sort data from the most used segments to the least used segments */ + +static int sort_most_used(const void *p1, const void *p2) +{ + const struct collected_data *c1 = p1; + const struct collected_data *c2 = p2; + if (c1->ios < c2->ios) return 1; + if (c1->ios > c2->ios) return -1; + if (c1->idx < c2->idx) return -1; + if (c1->idx > c2->idx) return 1; + return 0; +} + +/* + * Do automatic subdivision on a given range. + * We sort the segments according to the number of i/os in them, + * then we take at most "r->auto_subdivide_number" segments with most i/o counts + * and we create new ranges for these requests. + * + * The arguments for the subdivision are stored in r->auto_subdivide_* (they + * were loaded from the kernel from the auxiliary string). + */ + +static void do_auto_subdivide(struct range *r) +{ + size_t i, limit; + query_range(r); + do_message_args("@stats_set_aux %d %s", r->id, + r->auto_subdivided ? "auto-subdivided" : "-"); + qsort(r->data, r->steps, sizeof(struct collected_data), sort_most_used); + limit = 0; + for (i = 0; i < r->steps && i < r->auto_subdivide_number && r->data[i].ios; i++) + if (i + 1 >= r->steps || r->data[i].ios > r->data[i + 1].ios) + limit = i + 1; + for (i = 0; i < limit; i++) { + uint64_t sub_start, sub_end, sub_step; + char *sub_string; + sub_start = r->start + r->data[i].idx * r->step; + sub_end = sub_start + r->step; + if (sub_end > r->start + r->len) + sub_end = r->start + r->len; + + sub_step = r->step / r->auto_subdivide_granularity; + if (!sub_step) + sub_step = 1; + if (sub_step >= sub_end - sub_start) + continue; + + sub_string = NULL; + + if (r->auto_subdivide_depth > 1) + sub_string = make_auto_subdivide_string_from_args(r->auto_subdivide_granularity, r->auto_subdivide_time, r->auto_subdivide_depth - 1, r->auto_subdivide_number); + + do_message_args("@stats_create %"PRIu64"+%"PRIu64" %"PRIu64" %s auto-subdivided%s%s", + sub_start, + sub_end - sub_start, + sub_step, + LVSTATS_NAME, + sub_string ? "/" : "", + sub_string ? sub_string : "" + ); + } +} + +/* Check for ranges that need auto subdivision */ + +static void check(void) +{ + size_t x; + for (x = 0; x < n_ranges; x++) { + struct range *r = &ranges[x]; + if (clear && is_range_selected(r)) { + do_message_args("@stats_clear %d", r->id); + } + if (!r->auto_subdivide) + continue; + if ((uint64_t)time(NULL) > r->auto_subdivide_limit) { + do_auto_subdivide(r); + } + } +} + +/* Activate auto subdivide on the device */ + +static void set_auto_subdivide(void) +{ + char *auto_subdivide_string; + size_t x; + size_t best = (size_t)-1; + for (x = 0; x < n_ranges; x++) { + struct range *r = &ranges[x]; + if (r->auto_subdivide) { + lvstats_error("%s: device has already auto subdivide", device_name); + return; + } + if (r->auto_subdivided) + continue; + if (best == (size_t)-1 || ranges[best].len > ranges[x].len) + best = x; + } + if (best == (size_t)-1) { + lvstats_error("%s: no existing range to be auto-subdivided", device_name); + return; + } + auto_subdivide_string = make_auto_subdivide_string(); + do_message_args("@stats_set_aux %d %s", + ranges[best].id, + auto_subdivide_string); +} + +/* Sort ranges for the purpose of display */ + +static int range_compare(const void *p1, const void *p2) +{ + const struct range *r1 = p1; + const struct range *r2 = p2; + if (r1->len < r2->len) return 1; + if (r1->len > r2->len) return -1; + if (r1->start < r2->start) return -1; + if (r1->start > r2->start) return 1; + return 0; +} + +static void read_ranges(void) +{ + char *stats_list; + + ranges = NULL; + n_ranges = 0; + + stats_list = do_message_args("@stats_list %s", LVSTATS_NAME); + if (!stats_list) + lvstats_error("%s: no list returned for", device_name); + break_into_lines(stats_list, get_range); + qsort(ranges, n_ranges, sizeof(struct range), range_compare); +} + +static void update_lv_ranges(void) +{ + size_t i; + struct lv_statistics *ls; + dm_list_init(&logical_volume->statistics); + for (i = 0; i < n_ranges; i++) { + struct range *r = &ranges[i]; + if (r->auto_subdivided) + continue; + ls = dm_pool_alloc(command_pool, sizeof(struct lv_statistics)); + if (!ls) + lvstats_error("Out of memory for lv_statistics"); + ls->start = r->start; + ls->end = r->start + r->len; + ls->step = r->step; + dm_list_add(&logical_volume->statistics, &ls->list); + } +} + +/* + * This function does the requested operation for a single device specified + * by global variable "device_name". + */ + +static void do_operation_for_device(void) +{ + read_ranges(); + + switch (mode) { + case MODE_CHECK: + if (auto_subdivide) + set_auto_subdivide(); + check(); + break; + case MODE_CREATE: + create(); + break; + case MODE_DELETE: + del(auto_subdivide); + break; + case MODE_QUERY: + query(); + break; + case MODE_LIST: + list(); + break; + } + + if (update_metadata && logical_volume) { + read_ranges(); + update_lv_ranges(); + } +} + +/* Initialize the per-device pool */ + +static void lvstats_init_pool(void) +{ + if (device_pool) + lvstats_error("Pool already allocated"); + device_pool = dm_pool_create("lvstats", 4096); + if (!device_pool) + lvstats_error("Can't allocate pool"); +} + +/* Free the per-device pool */ + +static void lvstats_free_pool(void) +{ + if (!device_pool) + lvstats_error("Pool not allocated"); + dm_pool_destroy(device_pool); + device_pool = NULL; +} + +/* + * We can't do setjmp across process_each_lv (it results in lock leaks, file + * leaks and memory leaks), so we use this wrapper to catch errors. + */ + +static int do_operation_for_device_and_handle_error(void) +{ + jmp_buf * volatile previous_jmp = jmp; + jmp_buf jmp_error; + + lvstats_init_pool(); + + jmp = &jmp_error; + if (setjmp(*jmp)) { + jmp = previous_jmp; + return ECMD_FAILED; + } + + do_operation_for_device(); + + lvstats_free_pool(); + + jmp = previous_jmp; + + return ECMD_PROCESSED; +} + +static int sync_metadata(void) +{ + if (!vg_write(logical_volume->vg)) + return_0; + if (!vg_commit(logical_volume->vg)) + return 0; + return ECMD_PROCESSED; +} + +/* + * Do the specified operation for all device mapper devices. + * This function bypasses lvm, it takes a list of active device mapper + * devices and operates on all of them. + */ + +static void do_operation_for_all_dm_devices(void) +{ + struct dirent *de; + + dir = opendir(SYS_BLOCK); + if (!dir) + lvstats_error("Unable to open %s: %s", SYS_BLOCK, strerror(errno)); + while (errno = 0, de = readdir(dir)) { + if (strlen(de->d_name) >= 4 && + !memcmp(de->d_name, "dm-", 3)) { + char *dn1, *dn2; + int r; + dn1 = lvstats_malloc(strlen(DEV) + strlen(de->d_name) + 1); + strcpy(dn1, DEV); + strcat(dn1, de->d_name); + dn2 = get_dm_name(dn1); + device_name = dn2; + r = do_operation_for_device_and_handle_error(); + if (r > retval) + retval = r; + } + } + if (errno) + lvstats_error("Unable to read %s: %s", SYS_BLOCK, strerror(errno)); + if (closedir(dir)) + lvstats_error("Unable to close %s: %s", SYS_BLOCK, strerror(errno)); + dir = NULL; +} + +/* Do the specified operation for a logical volume */ + +static int do_operation_for_lv(struct cmd_context *cmd, struct logical_volume *lv, + void *handle __attribute__((unused))) +{ + int r; + struct lvinfo info; + char *name; + + if (!(lv_info(lv->vg->cmd, lv, 0, &info, 0, 0) && info.exists && info.live_table)) { + if (lv_specified) { + log_error("Logical volume %s/%s is not active", lv->vg->name, lv->name); + return ECMD_FAILED; + } + return ECMD_PROCESSED; + } + name = dm_build_dm_name(cmd->mem, lv->vg->name, lv->name, NULL); + if (!name) { + log_error("Unable to build lv name for %s/%s", lv->vg->name, lv->name); + return ECMD_FAILED; + } + + device_name = name; + logical_volume = lv; + + r = do_operation_for_device_and_handle_error(); + if (r != ECMD_PROCESSED) + goto ret; + + if (update_metadata) { + r = sync_metadata(); + } + +ret: + logical_volume = NULL; + return r; +} + +/* The main command routine */ + +int lvstats(struct cmd_context *cmd, int argc, char **argv) +{ + jmp_buf jmp_error; + + jmp = &jmp_error; + if (setjmp(*jmp)) { + retval = ECMD_FAILED; + goto ret; + } + + command_pool = cmd->mem; + + if (arg_count(cmd, dm_ARG)) + dm = true; + + if (arg_count(cmd, create_ARG)) { + if (mode != MODE_CHECK) +multiple_mode: + lvstats_error("Only one of mode switches (-c, -d, -q, -l) may be specified"); + mode = MODE_CREATE; + } + if (arg_count(cmd, delete_ARG)) { + if (mode != MODE_CHECK) + goto multiple_mode; + mode = MODE_DELETE; + } + if (arg_count(cmd, query_ARG)) { + if (mode != MODE_CHECK) + goto multiple_mode; + mode = MODE_QUERY; + } + if (arg_count(cmd, list_ARG)) { + if (mode != MODE_CHECK) + goto multiple_mode; + mode = MODE_LIST; + } + + if (arg_count(cmd, clear_ARG)) { + if (mode != MODE_QUERY && mode != MODE_CHECK) + lvstats_error("Parameter --clear can't be used with create, delete or list"); + clear = true; + } + + if (arg_count(cmd, raw_ARG)) { + if (mode != MODE_QUERY) + lvstats_error("Parameter --raw only allowed in query mode"); + raw = true; + } + + if (arg_count(cmd, nomerge_ARG)) { + if (mode != MODE_QUERY) + lvstats_error("Parameter --nomerge only allowed in query mode"); + nomerge = true; + } + + if (arg_count(cmd, seek_ARG)) { + if (mode != MODE_QUERY) + lvstats_error("Parameter --seek only allowed in query mode"); + seek = true; + } + + if (arg_count(cmd, start_ARG)) { + have_start_sector = true; + start_sector = arg_uint64_value(cmd, start_ARG, 0); + } + if (arg_count(cmd, end_ARG)) { + if (!have_start_sector) + lvstats_error("You must not specify end argument without start"); + have_end_sector = true; + end_sector = arg_uint64_value(cmd, end_ARG, 0); + if (end_sector <= start_sector) + lvstats_error("End sector must be greater than start sector"); + } + + if (arg_count(cmd, granularity_ARG) && arg_count(cmd, granularity_segments_ARG)) + lvstats_error("Only one of --granularity and --granularity-segments may be specified"); + if (arg_count(cmd, granularity_ARG)) { + granularity = arg_uint64_value(cmd, granularity_ARG, 0); + if (!granularity) + lvstats_error("Granularity must not be 0"); + } + if (arg_count(cmd, granularity_segments_ARG)) { + granularity_n = arg_uint_value(cmd, granularity_segments_ARG, 0); + if (!granularity_n) + lvstats_error("Granularity_segements must not be 0"); + } + if (arg_count(cmd, granularity_ARG) && arg_count(cmd, granularity_segments_ARG)) { + lvstats_error("Only one of --granularity and --granularity-segments allowed"); + } + + if (arg_count(cmd, auto_subdivide_ARG)) { + if (mode != MODE_CREATE && mode != MODE_CHECK && mode != MODE_DELETE) + lvstats_error("Auto subdivide can only be specified with -c or -d"); + auto_subdivide = true; + } + + if (auto_subdivide && mode != MODE_DELETE) { + auto_subdivide_granularity = arg_uint_value(cmd, auto_subdivide_segments_ARG, AUTO_SUBDIVIDE_GRANULARITY); + auto_subdivide_time = arg_uint_value(cmd, auto_subdivide_time_ARG, AUTO_SUBDIVIDE_TIME); + auto_subdivide_depth = arg_uint_value(cmd, auto_subdivide_depth_ARG, AUTO_SUBDIVIDE_DEPTH); + auto_subdivide_number = arg_uint_value(cmd, auto_subdivide_number_ARG, AUTO_SUBDIVIDE_NUMBER); + } else { + if (arg_count(cmd, auto_subdivide_segments_ARG)) + lvstats_error("--auto-subdivide-segments can only be used with auto subdivide"); + if (arg_count(cmd, auto_subdivide_time_ARG)) + lvstats_error("--auto-subdivide-time can only be used with auto subdivide"); + if (arg_count(cmd, auto_subdivide_depth_ARG)) + lvstats_error("--auto-subdivide-depth can only be used with auto subdivide"); + if (arg_count(cmd, auto_subdivide_number_ARG)) + lvstats_error("--auto-subdivide-number can only be used with auto subdivide"); + } + + /* + * if we are run without arguments just to do auto subdivide, bypass + * lvm and run in dm mode. + */ + if (!argc && mode == MODE_CHECK && !auto_subdivide && !clear) + dm = true; + + update_metadata = !dm && (mode == MODE_CREATE || mode == MODE_DELETE); + + if (!dm) { + int r; + lv_specified = !!argc; + r = process_each_lv(cmd, argc, argv, + update_metadata ? READ_FOR_UPDATE : 0, + NULL, do_operation_for_lv); + if (r > retval) + retval = r; + } else { + if (!argc) { + do_operation_for_all_dm_devices(); + } else { + int i; + + for (i = 0; i < argc; i++) { + int r; + device_name = argv[i]; + r = do_operation_for_device_and_handle_error(); + if (r > retval) + retval = r; + } + } + } + +ret: + /* close directory handler in case we jumped here from the error handler */ + if (dir) + closedir(dir), dir = NULL; + return retval; +} + Index: lvm2-copy/tools/Makefile =================================================================== --- lvm2-copy.orig/tools/Makefile 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/tools/Makefile 2013-10-20 19:51:07.000000000 +0200 @@ -32,6 +32,7 @@ SOURCES =\ lvrename.c \ lvresize.c \ lvscan.c \ + lvstats.c \ polldaemon.c \ pvchange.c \ pvck.c \ @@ -162,7 +163,7 @@ liblvm2cmd.$(LIB_SUFFIX).$(LIB_VERSION): .commands: $(srcdir)/commands.h $(srcdir)/cmdnames.h Makefile $(CC) -E -P $(srcdir)/cmdnames.h 2> /dev/null | \ - egrep -v '^ *(|#.*|dumpconfig|formats|help|pvdata|segtypes|version) *$$' > .commands + egrep -v '^ *(|#.*|devtypes|dumpconfig|formats|help|pvdata|segtypes|version) *$$' > .commands ifneq ("$(CFLOW_CMD)", "") CFLOW_SOURCES = $(addprefix $(srcdir)/, $(SOURCES)) Index: lvm2-copy/tools/commands.h =================================================================== --- lvm2-copy.orig/tools/commands.h 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/tools/commands.h 2013-10-28 00:04:53.000000000 +0100 @@ -545,6 +545,26 @@ xx(lvscan, all_ARG, blockdevice_ARG, ignorelockingfailure_ARG, partial_ARG) +xx(lvstats, + "Collect or display statistics", + PERMITTED_READ_ONLY, + "lvstats\n" + "\t[-c|--create]\n" + "\t[-d|--delete]\n" + "\t[-q|--query]\n" + "\t[-l|--list]\n" + "\t[--clear]\n" + "\t[-g|--granularity SegmentSize[bBsSkKmMgGtTpPeE]]\n" + "\t[--granularity-segments #segments]\n" + "\t[-s|--subdivide position[bBsSkKmMgGtTpPeE]]\n" + "\t[LogicalVolume[Path] [LogicalVolume[Path]...]]\n", + + create_ARG, delete_ARG, query_ARG, list_ARG, force_ARG, clear_ARG, + raw_ARG, nomerge_ARG, seek_ARG, granularity_ARG, granularity_segments_ARG, + start_ARG, end_ARG, dm_ARG, + auto_subdivide_ARG, auto_subdivide_segments_ARG, auto_subdivide_time_ARG, + auto_subdivide_number_ARG, auto_subdivide_depth_ARG) + xx(pvchange, "Change attributes of physical volume(s)", 0, Index: lvm2-copy/tools/args.h =================================================================== --- lvm2-copy.orig/tools/args.h 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/tools/args.h 2013-10-20 20:18:55.000000000 +0200 @@ -101,6 +101,14 @@ arg(profile_ARG, '\0', "profile", string arg(detachprofile_ARG, '\0', "detachprofile", NULL, 0) arg(mergedconfig_ARG, '\0', "mergedconfig", NULL, 0) arg(ignoreskippedcluster_ARG, '\0', "ignoreskippedcluster", NULL, 0) +arg(clear_ARG, '\0', "clear", NULL, 0) +arg(granularity_segments_ARG, '\0', "granularity-segments", int_arg, 0) +arg(seek_ARG, '\0', "seek", NULL, 0) +arg(dm_ARG, '\0', "dm", NULL, 0) +arg(auto_subdivide_segments_ARG, '\0', "auto-subdivide-segments", int_arg, 0) +arg(auto_subdivide_time_ARG, '\0', "auto-subdivide-time", int_arg, 0) +arg(auto_subdivide_depth_ARG, '\0', "auto-subdivide-depth", int_arg, 0) +arg(auto_subdivide_number_ARG, '\0', "auto-subdivide-number", int_arg, 0) /* Allow some variations */ arg(resizable_ARG, '\0', "resizable", yes_no_arg, 0) @@ -113,6 +121,7 @@ arg(available_ARG, '\0', "available", ac arg(activate_ARG, 'a', "activate", activation_arg, 0) arg(all_ARG, 'a', "all", NULL, 0) arg(autobackup_ARG, 'A', "autobackup", yes_no_arg, 0) +arg(auto_subdivide_ARG, 'a', "auto-subdivide", NULL, 0) arg(activevolumegroups_ARG, 'A', "activevolumegroups", NULL, 0) arg(background_ARG, 'b', "background", NULL, 0) arg(backgroundfork_ARG, 'b', "background", NULL, 0) @@ -122,12 +131,16 @@ arg(clustered_ARG, 'c', "clustered", yes arg(colon_ARG, 'c', "colon", NULL, 0) arg(columns_ARG, 'C', "columns", NULL, 0) arg(contiguous_ARG, 'C', "contiguous", yes_no_arg, 0) +arg(create_ARG, 'c', "create", NULL, 0) arg(debug_ARG, 'd', "debug", NULL, ARG_COUNTABLE) +arg(delete_ARG, 'd', "delete", NULL, 0) +arg(end_ARG, 'e', "end", size_mb_arg, 0) arg(exported_ARG, 'e', "exported", NULL, 0) arg(physicalextent_ARG, 'E', "physicalextent", NULL, 0) arg(file_ARG, 'f', "file", string_arg, 0) arg(force_ARG, 'f', "force", NULL, ARG_COUNTABLE) arg(full_ARG, 'f', "full", NULL, 0) +arg(granularity_ARG, 'g', "granularity", size_mb_arg, 0) arg(help_ARG, 'h', "help", NULL, 0) arg(help2_ARG, '?', "", NULL, 0) arg(stripesize_ARG, 'I', "stripesize", size_kb_arg, 0) @@ -149,6 +162,7 @@ arg(ignoreactivationskip_ARG, 'K', "igno arg(mirrors_ARG, 'm', "mirrors", int_arg_with_sign, 0) arg(metadatatype_ARG, 'M', "metadatatype", metadatatype_arg, 0) arg(maps_ARG, 'm', "maps", NULL, 0) +arg(nomerge_ARG, 'm', "nomerge", NULL, 0) arg(name_ARG, 'n', "name", string_arg, 0) arg(oldpath_ARG, 'n', "oldpath", NULL, 0) arg(nofsck_ARG, 'n', "nofsck", NULL, 0) @@ -159,7 +173,9 @@ arg(permission_ARG, 'p', "permission", p arg(maxphysicalvolumes_ARG, 'p', "maxphysicalvolumes", int_arg, 0) arg(partial_ARG, 'P', "partial", NULL, 0) arg(physicalvolume_ARG, 'P', "physicalvolume", NULL, 0) +arg(query_ARG, 'q', "query", NULL, 0) arg(quiet_ARG, 'q', "quiet", NULL, ARG_COUNTABLE) +arg(raw_ARG, 'r', "raw", NULL, 0) arg(readahead_ARG, 'r', "readahead", readahead_arg, 0) arg(resizefs_ARG, 'r', "resizefs", NULL, 0) arg(reset_ARG, 'R', "reset", NULL, 0) @@ -168,6 +184,7 @@ arg(physicalextentsize_ARG, 's', "physic arg(stdin_ARG, 's', "stdin", NULL, 0) arg(snapshot_ARG, 's', "snapshot", NULL, 0) arg(short_ARG, 's', "short", NULL, 0) +arg(start_ARG, 's', "start", size_mb_arg, 0) arg(thin_ARG, 'T', "thin", NULL, 0) arg(test_ARG, 't', "test", NULL, 0) arg(uuid_ARG, 'u', "uuid", NULL, 0) Index: lvm2-copy/lib/metadata/lv_manip.c =================================================================== --- lvm2-copy.orig/lib/metadata/lv_manip.c 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/lib/metadata/lv_manip.c 2013-10-20 15:40:35.000000000 +0200 @@ -4283,6 +4283,7 @@ struct logical_volume *alloc_lv(struct d dm_list_init(&lv->tags); dm_list_init(&lv->segs_using_this_lv); dm_list_init(&lv->rsites); + dm_list_init(&lv->statistics); return lv; } Index: lvm2-copy/lib/metadata/lv.h =================================================================== --- lvm2-copy.orig/lib/metadata/lv.h 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/lib/metadata/lv.h 2013-10-20 15:40:35.000000000 +0200 @@ -49,6 +49,7 @@ struct logical_volume { struct dm_list segments; struct dm_list tags; struct dm_list segs_using_this_lv; + struct dm_list statistics; uint64_t timestamp; const char *hostname; Index: lvm2-copy/lib/format_text/export.c =================================================================== --- lvm2-copy.orig/lib/format_text/export.c 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/lib/format_text/export.c 2013-10-20 15:40:35.000000000 +0200 @@ -586,6 +586,33 @@ int out_areas(struct formatter *f, const return 1; } +static int _print_lv_statistics(struct formatter *f, struct logical_volume *lv) +{ + struct lv_statistics *ls; + unsigned n_stats; + + n_stats = 0; + dm_list_iterate_items(ls, &lv->statistics) + n_stats++; + + if (!n_stats) + return 1; + + outnl(f); + outf(f, "statistics_count = %u", n_stats); + outnl(f); + + n_stats = 0; + dm_list_iterate_items(ls, &lv->statistics) { + n_stats++; + outf(f, "statistics%u_start = %"PRIu64, n_stats, ls->start); + outf(f, "statistics%u_end = %"PRIu64, n_stats, ls->end); + outf(f, "statistics%u_step = %"PRIu64, n_stats, ls->step); + } + + return 1; +} + static int _print_lv(struct formatter *f, struct logical_volume *lv) { struct lv_segment *seg; @@ -654,6 +681,9 @@ static int _print_lv(struct formatter *f return_0; } + if (!_print_lv_statistics(f, lv)) + return_0; + _dec_indent(f); outf(f, "}"); Index: lvm2-copy/lib/format_text/import_vsn1.c =================================================================== --- lvm2-copy.orig/lib/format_text/import_vsn1.c 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/lib/format_text/import_vsn1.c 2013-10-20 15:40:35.000000000 +0200 @@ -515,6 +515,46 @@ static int _read_segments(struct logical return 1; } +static int _read_statistics_entry(struct logical_volume *lv, + const struct dm_config_node *lvn, unsigned i, + const char *entry, uint64_t *value) +{ + char string[64]; + snprintf(string, sizeof string, "statistics%u_%s", i, entry); + if (!_read_uint64(lvn, string, value)) { + log_error("Invalid %s for logical volume %s.", + string, lv->name); + return 0; + } + return 1; +} + +static int _read_statistics(struct logical_volume *lv, + const struct dm_config_node *lvn, + struct dm_pool *mem) +{ + uint32_t n_statistics, i; + if (!dm_config_has_node(lvn, "statistics_count")) + return 1; + if (!_read_uint32(lvn, "statistics_count", &n_statistics)) { + log_error("Invalid statistics_count for logical volume %s.", + lv->name); + return 0; + } + for (i = 0; i < n_statistics; i++) { + struct lv_statistics *ls = dm_pool_alloc(mem, + sizeof(struct lv_statistics)); + if (!_read_statistics_entry(lv, lvn, i + 1, "start", &ls->start)) + return_0; + if (!_read_statistics_entry(lv, lvn, i + 1, "end", &ls->end)) + return_0; + if (!_read_statistics_entry(lv, lvn, i + 1, "step", &ls->step)) + return_0; + dm_list_add(&lv->statistics, &ls->list); + } + return 1; +} + static int _read_lvnames(struct format_instance *fid __attribute__((unused)), struct volume_group *vg, const struct dm_config_node *lvn, const struct dm_config_node *vgn __attribute__((unused)), @@ -608,6 +648,9 @@ static int _read_lvnames(struct format_i return 0; } + if (!_read_statistics(lv, lvn, vg->vgmem)) + return_0; + if (!dm_hash_insert(lv_hash, lv->name, lv)) return_0; Index: lvm2-copy/lib/activate/dev_manager.c =================================================================== --- lvm2-copy.orig/lib/activate/dev_manager.c 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/lib/activate/dev_manager.c 2013-10-20 15:40:35.000000000 +0200 @@ -2421,6 +2421,11 @@ static int _add_new_lv_to_dtree(struct d laopts->noscan ? LVM_UDEV_NOSCAN_FLAG : 0)))) return_0; + if (!layer) { + if (!dm_tree_node_add_statistics(dnode, &lv->statistics)) + return_0; + } + /* Store existing name so we can do rename later */ lvlayer->old_name = dm_tree_node_get_name(dnode); Index: lvm2-copy/libdm/libdevmapper.h =================================================================== --- lvm2-copy.orig/libdm/libdevmapper.h 2013-10-20 15:40:34.000000000 +0200 +++ lvm2-copy/libdm/libdevmapper.h 2013-10-20 15:40:35.000000000 +0200 @@ -479,6 +479,7 @@ int dm_driver_version(char *version, siz ******************************************************/ struct dm_tree; struct dm_tree_node; +struct dm_list; /* * Initialise an empty dependency tree. @@ -520,6 +521,7 @@ struct dm_tree_node *dm_tree_add_new_dev int clear_inactive, void *context, uint16_t udev_flags); +int dm_tree_node_add_statistics(struct dm_tree_node *dnode, struct dm_list *stats); /* * Search for a node in the tree. @@ -1788,6 +1790,15 @@ int dm_udev_wait(uint32_t cookie); #define DM_DEV_DIR_UMASK 0022 #define DM_CONTROL_NODE_UMASK 0177 +#define LVSTATS_NAME "lvstats" + +struct lv_statistics { + struct dm_list list; + uint64_t start; + uint64_t end; + uint64_t step; +}; + #ifdef __cplusplus } #endif Index: lvm2-copy/libdm/libdm-deptree.c =================================================================== --- lvm2-copy.orig/libdm/libdm-deptree.c 2013-10-20 15:40:26.000000000 +0200 +++ lvm2-copy/libdm/libdm-deptree.c 2013-10-28 01:08:01.000000000 +0100 @@ -262,6 +262,8 @@ struct dm_tree_node { struct load_properties props; /* For creation/table (re)load */ + struct dm_list statistics; + /* * If presuspend of child node is needed * Note: only direct child is allowed @@ -489,6 +491,7 @@ static struct dm_tree_node *_create_dm_t dm_list_init(&node->uses); dm_list_init(&node->used_by); dm_list_init(&node->props.segs); + dm_list_init(&node->statistics); dev = MKDEV((dev_t)info->major, info->minor); @@ -1147,6 +1150,25 @@ struct dm_tree_node *dm_tree_add_new_dev read_only, clear_inactive, context, 0); } +int dm_tree_node_add_statistics(struct dm_tree_node *dnode, struct dm_list *stats) +{ + struct lv_statistics *ls; + dm_list_init(&dnode->statistics); + dm_list_iterate_items(ls, stats) { + struct lv_statistics *n; + n = dm_pool_alloc(dnode->dtree->mem, sizeof(struct lv_statistics)); + if (!n) { + log_error("lv_statistics allocation failed"); + return 0; + } + n->start = ls->start; + n->end = ls->end; + n->step = ls->step; + dm_list_add(&dnode->statistics, &n->list); + } + return 1; +} + static struct dm_tree_node *_add_dev(struct dm_tree *dtree, struct dm_tree_node *parent, uint32_t major, uint32_t minor, @@ -1901,6 +1923,47 @@ out: return r; } +static void _set_statistics(struct dm_tree_node *dnode) +{ + struct dm_task *dmt; + struct lv_statistics *ls; + + if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) { + log_error("Create dm_task creation failed for %s", dnode->name); + return; + } + + if (!dm_task_set_uuid(dmt, dnode->uuid)) { + log_error("Failed to set uuid for %s", dnode->name); + goto out; + } + + if (!dm_task_set_sector(dmt, 0)) { + log_error("Failed to set sector for %s", dnode->name); + goto out; + } + + dm_list_iterate_items(ls, &dnode->statistics) { + char *message; + if (dm_asprintf(&message, "@stats_create %"PRIu64"+%"PRIu64" %"PRIu64" %s", ls->start, ls->end - ls->start, ls->step, LVSTATS_NAME) < 0) { + log_error("dm_asprintf failed for %s", dnode->name); + continue; + } + if (!dm_task_set_message(dmt, message)) { + log_error("Failed to set message for %s", dnode->name); + dm_free(message); + continue; + } + dm_free(message); + + if (!dm_task_run(dmt)) { + log_error("Failed to set statistics for %s", dnode->name); + } + } + +out: + dm_task_destroy(dmt); +} static int _build_dev_string(char *devbuf, size_t bufsize, struct dm_tree_node *node) { @@ -2563,8 +2626,11 @@ int dm_tree_preload_children(struct dm_t return_0; /* FIXME Cope if name exists with no uuid? */ - if (!child->info.exists && !_create_node(child)) - return_0; + if (!child->info.exists) { + if (!_create_node(child)) + return_0; + _set_statistics(child); + } if (!child->info.inactive_table && child->props.segment_count &&