#define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include /* * stats * create: * -c [ ] [ -g ] * delete: * -d [ ] [ -s starting_sector ] * query: * -q [ ] [ --clear ] [ --raw ] [ --bi ] * clear: * --clear [ ] * subdivide: * [ ] -s starting_sector [ -g ] * auto subdivide: * [ ] -a [ auto-subdivide-* ] * delete auto subdivided ranges: * [ ] -ad */ extern void *xmalloc(size_t); extern void *xrealloc(void *, size_t); extern char *xstrdup(const char *); extern char *xstrndup(const char *, size_t); #define SYS_BLOCK "/sys/block" #define DEV "/dev/" #define STAT_NAME "statistics" #define LOCK "/var/lock/dm-stats" #define ERROR_STATUS 1 #define ERROR_PARAMS 4 #define GRANULARITY_DEFAULT (65536 * 1024 / 512) #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 int retval = 0; static const char *device_name; static int device_specified; #define MODE_CHECK 0 #define MODE_CREATE 1 #define MODE_DELETE 2 #define MODE_QUERY 3 #define MODE_LIST 4 static int mode = MODE_CHECK; static int clear = 0; static int force = 0; static int no_lock = 0; static int raw = 0; static int bi = 0; static int nomerge = 0; static int seek_time = 0; static long long granularity = 0; static int do_subdivide = 0; static long long subdivide = -1; static int auto_subdivide = 0; static int auto_subdivide_granularity = 0; static int auto_subdivide_time = 0; static int auto_subdivide_depth = 0; static int 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 all_data { struct collected_data *c; struct range *r; uint64_t start; uint64_t len; size_t depth; }; struct range { int id; uint64_t start; uint64_t end; uint64_t step; size_t steps; struct collected_data *data; bool auto_subdivided; bool auto_subdivide; int auto_subdivide_granularity; int auto_subdivide_time; int auto_subdivide_depth; int auto_subdivide_number; long long auto_subdivide_limit; }; static void break_into_lines(char *msg, void (*function)(const char *)) { while (*msg) { char *eol = strchr(msg, '\n'); if (!eol) error(ERROR_STATUS, 0, "%s: unterminated line: \"%s\"", device_name, msg); *eol = 0; function(msg); msg = eol + 1; } } static char *get_dm_name(const char *dev) { struct dm_task *dmt; char *name; if (!(dmt = dm_task_create(DM_DEVICE_INFO))) error(ERROR_STATUS, 0, "%s: dm_task_create(DM_DEVICE_TARGET_MSG) failed", dev); if (!dm_task_set_name(dmt, dev)) error(ERROR_STATUS, 0, "%s: dm_task_set_name failed", dev); if (!dm_task_run(dmt)) error(ERROR_STATUS, 0, "%s: DM_DEVICE_INFO ioctl failed", dev); name = xstrdup(dm_task_get_name(dmt)); dm_task_destroy(dmt); return name; } static char *do_message(const char *msg) { struct dm_task *dmt; const char *result; char *result_ret; if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) error(ERROR_STATUS, 0, "%s: dm_task_create(DM_DEVICE_TARGET_MSG) failed", device_name); if (!dm_task_set_name(dmt, device_name)) error(ERROR_STATUS, 0, "%s: dm_task_set_name failed", device_name); if (!dm_task_set_sector(dmt, 0)) error(ERROR_STATUS, 0, "%s: dm_task_set_sector failed", device_name); if (!dm_task_set_message(dmt, msg)) error(ERROR_STATUS, 0, "%s: dm_task_set_message(%s) failed", device_name, msg); errno = 0; if (!dm_task_run(dmt)) { if (errno == ENOMEM) error(ERROR_STATUS, 0, "%s: out of kernel memory", device_name); error(ERROR_STATUS, errno, "%s: dm_task_run(%s) failed", device_name, msg); } result = dm_task_get_message_result(dmt); if (!result) { result_ret = NULL; } else { result_ret = xstrdup(result); } dm_task_destroy(dmt); return result_ret; } #define MSG_NO_RETURN 1 __attribute__((__format__(__printf__, 2, 3))) static char *do_message_args(int flags, const char *msg, ...) { va_list va; char *str, *rs; va_start(va, msg); if (vasprintf(&str, msg, va) < 0) error(ERROR_STATUS, 0, "vasprintf failed (%s)", msg); va_end(va); rs = do_message(str); free(str); if (flags & MSG_NO_RETURN) free(rs), rs = NULL; return rs; } static char *make_auto_subdivide_string_from_args(int gran, int tim, int depth, int number) { char *auto_subdivide_string; if (asprintf(&auto_subdivide_string, "auto-subdivide:%d,%d,%d,%d,%lld", gran, tim, depth, number, (long long)time(NULL) + tim ) < 0) error(ERROR_STATUS, 0, "asprintf failed"); return auto_subdivide_string; } 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); } 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 xstrdup(""); if (*aux == ':') { aux++; return xstrndup(aux, strcspn(aux, "/")); } } aux = strchr(aux, '/'); if (!aux) return NULL; aux++; goto try_next; } static struct range *ranges; static size_t n_ranges; static void get_range(const char *line) { char *aux = NULL; char *autosub; uint64_t steps; ranges = xrealloc(ranges, (n_ranges + 1) * sizeof(struct range)); if (sscanf(line, "%d: %"SCNu64"-%"SCNu64" %"SCNu64" "STAT_NAME" %ms", &ranges[n_ranges].id, &ranges[n_ranges].start, &ranges[n_ranges].end, &ranges[n_ranges].step, &aux) < 5) invalid_line: error(ERROR_STATUS, 0, "%s: invalid status line \"%s\"", device_name, line); ranges[n_ranges].auto_subdivided = false; if ((autosub = find_aux_arg(aux, "auto-subdivided"))) { ranges[n_ranges].auto_subdivided = true; free(autosub); } ranges[n_ranges].auto_subdivide = false; if ((autosub = find_aux_arg(aux, "auto-subdivide"))) { if (sscanf(autosub, "%d,%d,%d,%d,%lld", &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; free(autosub); } if (aux) free(aux); steps = (ranges[n_ranges].end - ranges[n_ranges].start + ranges[n_ranges].step - 1) / ranges[n_ranges].step; if ((size_t)steps * sizeof(struct collected_data) / sizeof(struct collected_data) != steps) error(ERROR_STATUS, 0, "%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)) error(ERROR_STATUS, 0, "%s: n_ranges overflow", device_name); } static void free_ranges(void) { size_t x; for (x = 0; x < n_ranges; x++) { if (ranges[x].data) free(ranges[x].data); } if (ranges) free(ranges); ranges = NULL; n_ranges = 0; } static void do_delete(struct range *r) { if (auto_subdivide) if (!r->auto_subdivided) { if (r->auto_subdivide) do_message_args(MSG_NO_RETURN, "@stats_set_aux %d -", r->id); return; } do_message_args(MSG_NO_RETURN, "@stats_delete %d", r->id); } static void del(void) { size_t x; if (do_subdivide) { size_t best = (size_t)-1; for (x = 0; x < n_ranges; x++) { if (ranges[x].start == subdivide) { if (best == (size_t)-1 || ranges[best].end >= ranges[x].end) best = x; } } if (best == (size_t)-1) { if (!device_specified) return; error(ERROR_STATUS, 0, "%s: subdivide must match the start of existing range when deleting", device_name); } for (x = n_ranges; x > 0; ) { x--; if (ranges[x].start >= ranges[best].start && ranges[x].end <= ranges[best].end) do_delete(&ranges[x]); } } else { for (x = n_ranges; x > 0; ) { x--; do_delete(&ranges[x]); } } } static void create(void) { char *auto_subdivide_string = NULL; if (!granularity) granularity = GRANULARITY_DEFAULT; if (n_ranges) { if (force) { del(); } else { if (device_specified) error(ERROR_STATUS, 0, "%s: statistics already exist", device_name); return; } } if (auto_subdivide) { auto_subdivide_string = make_auto_subdivide_string(); } do_message_args(MSG_NO_RETURN, "@stats_create - %s%"PRIu64" %s%s%s", granularity >= 0 ? "" : "/", (uint64_t)llabs(granularity), STAT_NAME, auto_subdivide_string ? " " : "", auto_subdivide_string ? auto_subdivide_string : "" ); if (auto_subdivide_string) free(auto_subdivide_string); } static struct range *q_r; static size_t q_idx, q_len; static void get_stats(const char *line) { uint64_t start, end; 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_len) error(ERROR_STATUS, 0, "%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, &end, &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) error(ERROR_STATUS, 0, "%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_len--; } static void query_range(struct range *r) { q_r = r; if (q_r->data) free(q_r->data); q_r->data = xmalloc(q_r->steps * sizeof(struct collected_data)); for (q_idx = 0; q_idx < q_r->steps; ) { char *q; q_len = READ_BATCH; if (q_idx + q_len < q_idx || q_idx + q_len > q_r->steps) q_len = q_r->steps - q_idx; q = do_message_args(0, "@stats_print%s %d %zu %zu", clear ? "_clear" : "", q_r->id, q_idx, q_len); if (!q) error(ERROR_STATUS, 0, "%s: no data returned", device_name); break_into_lines(q, get_stats); if (q_len) error(ERROR_STATUS, 0, "%s: insufficient number of lines returned: %zu, %zu", device_name, q_idx, q_len); free(q); } } static int data_compare(const void *p1, const void *p2) { const struct all_data *a1 = p1; const struct all_data *a2 = p2; if (a1->start < a2->start) return -1; if (a1->start > a2->start) return 1; if (a1->len < a2->len) return 1; if (a1->len > a2->len) return -1; return 0; } 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; } static double transferred(uint64_t sectors) { return (double)sectors * (bi ? 512. / 1048576. : 512. / 1000000.); } 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.); } static double latency(uint64_t requests, uint64_t msec, uint64_t busy_msec) { if (!requests) return 0; if (seek_time) msec = busy_msec; return (double)msec / (double)requests; } static void query(void) { size_t x; size_t total_data = 0, pos; struct all_data *all; for (x = 0; x < n_ranges; x++) { query_range(&ranges[x]); if (total_data + ranges[x].steps < total_data) error(ERROR_STATUS, 0, "%s: integer overflow in total_data (%zu + %zu)", device_name, total_data, ranges[x].steps); total_data += ranges[x].steps; } if (total_data * sizeof(struct all_data) / sizeof(struct all_data) != total_data) error(ERROR_STATUS, 0, "%s: integer overflow in total_data (%zu)", device_name, total_data); if (mode != MODE_QUERY && !do_subdivide) return; all = xmalloc(total_data * sizeof(struct all_data)); pos = 0; for (x = 0; x < n_ranges; x++) { size_t y; uint64_t start = ranges[x].start; for (y = 0; y < ranges[x].steps; y++) { all[pos].c = &ranges[x].data[y]; all[pos].r = &ranges[x]; all[pos].start = start; all[pos].len = ranges[x].step; if (start + all[pos].len > ranges[x].end) all[pos].len = ranges[x].end - start; all[pos].depth = 0; start += ranges[x].step; pos++; } } qsort(all, total_data, sizeof(struct all_data), data_compare); for (x = 0; x < total_data; x++) { size_t y; for (y = x + 1; y < total_data; y++) { if (all[y].start >= all[x].start + all[x].len) break; all[y].depth++; } } if (do_subdivide) { long long g = granularity; for (x = total_data; x > 0; ) { x--; if (all[x].start == subdivide) goto subdivide_found; } if (!device_specified) goto skip_subdivide; error(ERROR_STATUS, 0, "%s: the subdivide argument must match the start of existing range", device_name); subdivide_found: if (all[x].len == 1) { if (!device_specified) goto skip_subdivide; error(ERROR_STATUS, 0, "%s: the range with 1 sector can't be subdivided", device_name); } if (!g) { g = 1; while (g < all[x].len / GRANULARITY_SUBDIVIDE) g *= 2; } if (g > 0 && g >= all[x].len) { if (!device_specified) goto skip_subdivide; error(ERROR_STATUS, 0, "%s: granularity must be smaller than existing range that is being subdivided", device_name); } do_message_args(MSG_NO_RETURN, "@stats_create %"PRIu64"-%"PRIu64" %s%"PRIu64" %s", all[x].start, all[x].start + all[x].len, g >= 0 ? "" : "/", (uint64_t)llabs(g), STAT_NAME ); } skip_subdivide: if (mode != MODE_QUERY) goto skip_print; if (total_data) if (!device_specified) printf("%s:\n", device_name); for (x = 0; x < total_data; x++) { size_t d; uint64_t end = all[x].start + all[x].len; if (end < all[x].start || end > all[x].r->end) end = all[x].r->end; putchar(' '); for (d = 0; d < all[x].depth; d++) putchar(' '); printf("%"PRIu64"-%"PRIu64":", all[x].start, end ); if (!raw) { printf(" %"PRIu64" %.1fM%sB %.3fM%sB/s %.3fms %"PRIu64" %.1fM%sB %.3fM%sB/s %.3fms", num_ios(all[x].c->reads, all[x].c->reads_merged), transferred(all[x].c->read_sectors), bi ? "i" : "", throughput(all[x].c->read_sectors, all[x].c->reading_msec), bi ? "i" : "", latency(num_ios(all[x].c->reads, all[x].c->reads_merged), all[x].c->read_msec, all[x].c->reading_msec), num_ios(all[x].c->writes, all[x].c->writes_merged), transferred(all[x].c->write_sectors), bi ? "i" : "", throughput(all[x].c->write_sectors, all[x].c->writing_msec), bi ? "i" : "", latency(num_ios(all[x].c->writes, all[x].c->writes_merged), all[x].c->write_msec, all[x].c->writing_msec) ); } else { printf(" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64"", all[x].c->reads, all[x].c->reads_merged, all[x].c->read_sectors, all[x].c->read_msec, all[x].c->writes, all[x].c->writes_merged, all[x].c->write_sectors, all[x].c->write_msec, all[x].c->io_in_progress, all[x].c->io_msec, all[x].c->weighted_io_msec, all[x].c->reading_msec, all[x].c->writing_msec ); } printf("\n"); } skip_print: free(all); } static void list(void) { size_t x; if (!n_ranges) return; if (!device_specified) printf("%s:\n", device_name); for (x = 0; x < n_ranges; x++) { printf("%"PRIu64"-%"PRIu64" / %"PRIu64"", ranges[x].start, ranges[x].end, ranges[x].step); if (ranges[x].auto_subdivided) printf(" auto-subdivided"); if (ranges[x].auto_subdivide) { long long now = time(NULL); printf(" auto-subdivide("); now = ranges[x].auto_subdivide_limit - now; if (now > 0) printf("time=%lld", now); else printf("expired"); if (ranges[x].auto_subdivide_depth > 1) printf(",depth=%d", ranges[x].auto_subdivide_depth); printf(")"); } putchar('\n'); } } 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; } static void do_auto_subdivide(struct range *r) { size_t i, limit; /*printf("found expiry: %lld > %lld\n", (long long)time(NULL), (long long)r->auto_subdivide_limit);*/ query_range(r); do_message_args(MSG_NO_RETURN, "@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; 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; /*printf("doing subdivide: %ld: %ld %ld\n", r->data[i].idx, r->data[i].reads, r->data[i].writes);*/ sub_start = r->start + r->data[i].idx * r->step; sub_end = sub_start + r->step; if (sub_end > r->end) sub_end = r->end; 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(MSG_NO_RETURN, "@stats_create %"PRIu64"-%"PRIu64" %"PRIu64" %s auto-subdivided%s%s", sub_start, sub_end, sub_step, STAT_NAME, sub_string ? "/" : "", sub_string ? sub_string : "" ); if (sub_string) free(sub_string); } } static void check(void) { size_t x; for (x = 0; x < n_ranges; x++) { struct range *r = &ranges[x]; if (!r->auto_subdivide) continue; if (time(NULL) > r->auto_subdivide_limit) { do_auto_subdivide(r); } } } 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) { if (device_specified) error(ERROR_STATUS, 0, "%s: device has already auto subdivide", device_name); return; } if (r->auto_subdivided) continue; if (best == (size_t)-1 || ranges[best].end - ranges[best].start > ranges[x].end - ranges[x].start) best = x; } if (best == (size_t)-1) { if (device_specified) error(ERROR_STATUS, 0, "%s: no existing range to be auto-subdivided", device_name); return; } auto_subdivide_string = make_auto_subdivide_string(); do_message_args(MSG_NO_RETURN, "@stats_set_aux %d %s", ranges[best].id, auto_subdivide_string); free(auto_subdivide_string); } static int range_compare(const void *p1, const void *p2) { const struct range *r1 = p1; const struct range *r2 = p2; if (r1->start < r2->start) return -1; if (r1->start > r2->start) return 1; if (r1->end < r2->end) return 1; if (r1->end > r2->end) return -1; return 0; } static void do_operation_for_device(void) { char *stats_list; ranges = NULL; n_ranges = 0; stats_list = do_message_args(0, "@stats_list %s", STAT_NAME); if (!stats_list) error(ERROR_STATUS, 0, "%s: no list returned for", device_name); break_into_lines(stats_list, get_range); free(stats_list); qsort(ranges, n_ranges, sizeof(struct range), range_compare); switch (mode) { case MODE_CHECK: if (auto_subdivide) set_auto_subdivide(); check(); if (clear || do_subdivide) query(); break; case MODE_CREATE: create(); break; case MODE_DELETE: del(); break; case MODE_QUERY: query(); break; case MODE_LIST: list(); break; } free_ranges(); } static int lock_handle = -1; static void lock_me(void) { int h, h2; struct stat st, st2; again: h = creat(LOCK, 0600); if (h == -1) error(ERROR_STATUS, errno, "Unable to create %s", LOCK); if (flock(h, LOCK_EX) == -1) error(ERROR_STATUS, errno, "Unable to lock %s", LOCK); h2 = open(LOCK, O_RDONLY); if (h2 == -1) { if (close(h)) error(ERROR_STATUS, errno, "Unable to close %s", LOCK); goto again; } if (fstat(h, &st)) error(ERROR_STATUS, errno, "Unable get stat for %s", LOCK); if (fstat(h2, &st2)) error(ERROR_STATUS, errno, "Unable get stat for %s", LOCK); if (!st.st_nlink || !(st.st_dev == st2.st_dev && st.st_ino == st2.st_ino)) { if (close(h)) error(ERROR_STATUS, errno, "Unable to close %s", LOCK); if (close(h2)) error(ERROR_STATUS, errno, "Unable to close %s", LOCK); goto again; } lock_handle = h; } static void unlock_me(void) { int h = lock_handle; if (h == -1) return; lock_handle = -1; if (unlink(LOCK)) error(ERROR_STATUS, errno, "Unable to unlink %s", LOCK); if (close(h)) error(ERROR_STATUS, errno, "Unable to close %s", LOCK); } static void help(poptContext popt_context, enum poptCallbackReason reason, struct poptOption *key, const char *arg, void *data) { if (!strcmp(key->longName, "help")) { poptPrintHelp(popt_context, stdout, 0); } else { char dm_version[256]; dm_get_library_version(dm_version, sizeof dm_version); printf("dm-stats; device mapper %s\n", dm_version); } exit(0); } static struct poptOption popt_help_options[] = { { NULL, 0, POPT_ARG_CALLBACK, help, 0, NULL, NULL }, { "help", 0, POPT_ARG_NONE, NULL, 0, "Show help", NULL }, { "version", 0, POPT_ARG_NONE, NULL, 0, "Show version", NULL }, POPT_TABLEEND }; static const struct poptOption popt_options[] = { { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, NULL, NULL }, { "create", 'c', POPT_ARG_VAL, &mode, MODE_CREATE, "Start collecting statistics", NULL }, { "delete", 'd', POPT_ARG_VAL, &mode, MODE_DELETE, "Delete collected statistics", NULL }, { "query", 'q', POPT_ARG_VAL, &mode, MODE_QUERY, "Query statistics", NULL }, { "list", 'l', POPT_ARG_VAL, &mode, MODE_LIST, "List ranges", NULL }, { "clear", 0, POPT_ARG_VAL, &clear, 1, "Clear statistics", NULL }, { "granularity", 'g', POPT_ARG_LONGLONG, &granularity, 0, "The nubmer of sectors in one area", NULL }, { "subdivide", 's', POPT_ARG_LONGLONG, &subdivide, 0, "Subdivide existing range", NULL }, { "auto-subdivide", 'a', POPT_ARG_VAL, &auto_subdivide, 1, "Automatically subdivide most used ranges", NULL }, { "auto-subdivide-granularity", 0, POPT_ARG_INT, &auto_subdivide_granularity, 0, "Granularity for automatic subdivide", NULL }, { "auto-subdivide-time",0, POPT_ARG_INT, &auto_subdivide_time, 0, "Automatically subdivide after the specified time in seconds", NULL }, { "auto-subdivide-number",0, POPT_ARG_INT, &auto_subdivide_number, 0, "Automatically subdivide this number of most used ranges", NULL }, { "auto-subdivide-depth", 0, POPT_ARG_INT, &auto_subdivide_depth, 0, "Auto subdivide depth", NULL }, { "force", 'f', POPT_ARG_VAL, &force, 1, "Create new statistics and delete existing data", NULL }, { "no-lock", 0, POPT_ARG_VAL, &no_lock, 1, "Do not lock for concurrent execution", NULL }, { "raw", 'r', POPT_ARG_VAL, &raw, 1, "Report raw values", NULL }, { "bi", 'i', POPT_ARG_VAL, &bi, 1, "Report data in mebibytes rather than megabytes", NULL }, { "nomerge", 'm', POPT_ARG_VAL, &nomerge, 1, "Don't join merged I/Os when reporting", NULL }, { "seek", 'e', POPT_ARG_VAL, &seek_time, 1, "Show seek time instead of latency", NULL }, POPT_TABLEEND }; int main(int argc, const char *argv[]) { poptContext popt_context; int r; const char *s; popt_context = poptGetContext("stats", argc, argv, popt_options, 0); poptSetOtherOptionHelp(popt_context, "[-c | -d | -q | -l] [] [OPTION...]"); r = poptGetNextOpt(popt_context); if (r < -1) error(ERROR_PARAMS, 0, "bad option %s", poptBadOption(popt_context, 0)); device_name = poptGetArg(popt_context); s = poptGetArg(popt_context); if (s) error(ERROR_PARAMS, 0, "extra argument %s", s); if (!no_lock) { lock_me(); atexit(unlock_me); } if (clear) { if (mode != MODE_QUERY && mode != MODE_CHECK) error(ERROR_PARAMS, 0, "clear parameter can't be used with start, stop or list"); } if (raw || bi) { if (mode != MODE_QUERY) error(ERROR_PARAMS, 0, "--raw or --bi is only allowed in query mode"); } if (granularity) { if (mode != MODE_CREATE && subdivide == -1) error(ERROR_PARAMS, 0, "granularity can only be specified with -c or -s"); } if (subdivide != -1) { if (subdivide < -1) error(ERROR_PARAMS, 0, "invalid subdivide argument"); if (mode != MODE_CHECK && mode != MODE_DELETE) error(ERROR_PARAMS, 0, "subdivide may not be specified with -c or -q"); if (granularity == -1) error(ERROR_PARAMS, 0, "granularity must not be -1 when subdividing"); do_subdivide = 1; } if (!auto_subdivide || mode == MODE_DELETE) { if (auto_subdivide_granularity) error(ERROR_PARAMS, 0, "auto-subdivide-granularity only allowed with auto-subdivde"); if (auto_subdivide_time) error(ERROR_PARAMS, 0, "auto-subdivide-time only allowed with auto-subdivde"); if (auto_subdivide_depth) error(ERROR_PARAMS, 0, "auto-subdivide-depth only allowed with auto-subdivde"); } else { if (mode != MODE_CREATE && mode != MODE_CHECK) error(ERROR_PARAMS, 0, "auto subidivide can only be specified with -c or -d"); if (auto_subdivide_granularity < 0) error(ERROR_PARAMS, 0, "auto-subdivide-granularity is negative"); if (!auto_subdivide_granularity) auto_subdivide_granularity = AUTO_SUBDIVIDE_GRANULARITY; if (auto_subdivide_time < 0) error(ERROR_PARAMS, 0, "auto-subdivide-time is negative"); if (!auto_subdivide_time) auto_subdivide_time = AUTO_SUBDIVIDE_TIME; if (auto_subdivide_depth < 0) error(ERROR_PARAMS, 0, "auto-subdivide-depth is negative"); if (!auto_subdivide_depth) auto_subdivide_depth = AUTO_SUBDIVIDE_DEPTH; if (auto_subdivide_number < 0) error(ERROR_PARAMS, 0, "auto-subdivide-number is negative"); if (!auto_subdivide_number) auto_subdivide_number = AUTO_SUBDIVIDE_NUMBER; } if (!device_name) { DIR *sys_block; struct dirent *de; device_specified = 0; sys_block = opendir(SYS_BLOCK); if (!sys_block) error(ERROR_STATUS, errno, "Unable to open %s", SYS_BLOCK); while (errno = 0, de = readdir(sys_block)) { if (strlen(de->d_name) >= 4 && !memcmp(de->d_name, "dm-", 3)) { char *dn1, *dn2; dn1 = xmalloc(strlen(DEV) + strlen(de->d_name) + 1); strcpy(dn1, DEV); strcat(dn1, de->d_name); dn2 = get_dm_name(dn1); free(dn1); device_name = dn2; do_operation_for_device(); free(dn2); } } if (errno) error(ERROR_STATUS, errno, "Unable to read %s", SYS_BLOCK); if (closedir(sys_block)) error(ERROR_STATUS, errno, "Unable to close %s", SYS_BLOCK); } else { device_specified = 1; do_operation_for_device(); } poptFreeContext(popt_context); return retval; }