/* * Copyright (C) 2015 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dmlib.h" #include "math.h" /* log10() */ #define DM_STATS_REGION_NOT_PRESENT UINT64_MAX #define NSEC_PER_USEC 1000L #define NSEC_PER_MSEC 1000000L #define NSEC_PER_SEC 1000000000L #define PRECISE_ARG "precise_timestamps" #define HISTOGRAM_ARG "histogram:" /* Histogram bin */ struct dm_histogram_bin { uint64_t upper; /* Upper bound on this bin. */ uint64_t count; /* Count value for this bin. */ }; struct dm_histogram { /* The stats handle this histogram belongs to. */ const struct dm_stats *dms; /* The region this histogram belongs to. */ const struct dm_stats_region *region; uint64_t sum; /* Sum of histogram bin counts. */ int nr_bins; /* Number of histogram bins assigned. */ struct dm_histogram_bin bins[0]; }; /* * See Documentation/device-mapper/statistics.txt for full descriptions * of the device-mapper statistics counter fields. */ struct dm_stats_counters { uint64_t reads; /* Num reads completed */ uint64_t reads_merged; /* Num reads merged */ uint64_t read_sectors; /* Num sectors read */ uint64_t read_nsecs; /* Num milliseconds spent reading */ uint64_t writes; /* Num writes completed */ uint64_t writes_merged; /* Num writes merged */ uint64_t write_sectors; /* Num sectors written */ uint64_t write_nsecs; /* Num milliseconds spent writing */ uint64_t io_in_progress; /* Num I/Os currently in progress */ uint64_t io_nsecs; /* Num milliseconds spent doing I/Os */ uint64_t weighted_io_nsecs; /* Weighted num milliseconds doing I/Os */ uint64_t total_read_nsecs; /* Total time spent reading in milliseconds */ uint64_t total_write_nsecs; /* Total time spent writing in milliseconds */ struct dm_histogram *histogram; /* Histogram. */ }; struct dm_stats_region { uint64_t region_id; /* as returned by @stats_list */ uint64_t start; uint64_t len; uint64_t step; char *program_id; char *aux_data; uint64_t timescale; /* precise_timestamps is per-region */ struct dm_histogram *bounds; /* histogram configuration */ struct dm_stats_counters *counters; }; struct dm_stats { /* device binding */ int major; /* device major that this dm_stats object is bound to */ int minor; /* device minor that this dm_stats object is bound to */ char *name; /* device-mapper device name */ char *uuid; /* device-mapper UUID */ char *program_id; /* default program_id for this handle */ struct dm_pool *mem; /* memory pool for region and counter tables */ struct dm_pool *hist_mem; /* separate pool for histogram tables */ uint64_t nr_regions; /* total number of present regions */ uint64_t max_region; /* size of the regions table */ uint64_t interval_ns; /* sampling interval in nanoseconds */ uint64_t timescale; /* default sample value multiplier */ int precise; /* use precise_timestamps when creating regions */ struct dm_stats_region *regions; /* statistics cursor */ uint64_t cur_region; uint64_t cur_area; }; #define PROC_SELF_COMM "/proc/self/comm" static char *_program_id_from_proc(void) { FILE *comm = NULL; char buf[256]; if (!(comm = fopen(PROC_SELF_COMM, "r"))) return_NULL; if (!fgets(buf, sizeof(buf), comm)) { log_error("Could not read from %s", PROC_SELF_COMM); if (fclose(comm)) stack; return NULL; } if (fclose(comm)) stack; return dm_strdup(buf); } static uint64_t _nr_areas(uint64_t len, uint64_t step) { /* Default is one area. */ if (!len || !step) return 1; /* * drivers/md/dm-stats.c::message_stats_create() * A region may be sub-divided into areas with their own counters. * Any partial area at the end of the region is treated as an * additional complete area. */ return (len + step - 1) / step; } static uint64_t _nr_areas_region(struct dm_stats_region *region) { return _nr_areas(region->len, region->step); } struct dm_stats *dm_stats_create(const char *program_id) { size_t hist_hint = sizeof(struct dm_histogram_bin); struct dm_stats *dms = NULL; if (!(dms = dm_zalloc(sizeof(*dms)))) return_NULL; /* FIXME: better hint. */ if (!(dms->mem = dm_pool_create("stats_pool", 4096))) { dm_free(dms); return_NULL; } if (!(dms->hist_mem = dm_pool_create("histogram_pool", hist_hint))) goto_bad; if (!program_id || !strlen(program_id)) dms->program_id = _program_id_from_proc(); else dms->program_id = dm_strdup(program_id); if (!dms->program_id) { dm_pool_destroy(dms->hist_mem); goto_bad; } dms->major = -1; dms->minor = -1; dms->name = NULL; dms->uuid = NULL; /* by default all regions use msec precision */ dms->timescale = NSEC_PER_MSEC; dms->precise = 0; dms->nr_regions = DM_STATS_REGION_NOT_PRESENT; dms->max_region = DM_STATS_REGION_NOT_PRESENT; dms->regions = NULL; return dms; bad: dm_pool_destroy(dms->mem); dm_free(dms); return NULL; } /** * Test whether the stats region pointed to by region is present. */ static int _stats_region_present(const struct dm_stats_region *region) { return !(region->region_id == DM_STATS_REGION_NOT_PRESENT); } static void _stats_histograms_destroy(struct dm_pool *mem, struct dm_stats_region *region) { /* Unpopulated handle. */ if (!region->counters) return; /* * Free everything in the pool back to the first histogram. */ if (region->counters[0].histogram) dm_pool_free(mem, region->counters[0].histogram); } static void _stats_region_destroy(struct dm_stats_region *region) { if (!_stats_region_present(region)) return; /** * Don't free counters here explicitly; it will be dropped * from the pool along with the corresponding regions table. * * The following objects are all allocated with dm_malloc. */ if (region->program_id) dm_free(region->program_id); if (region->aux_data) dm_free(region->aux_data); } static void _stats_regions_destroy(struct dm_stats *dms) { struct dm_pool *mem = dms->mem; uint64_t i; if (!dms->regions) return; /* walk backwards to obey pool order */ for (i = dms->max_region; (i != DM_STATS_REGION_NOT_PRESENT); i--) { _stats_histograms_destroy(dms->hist_mem, &dms->regions[i]); _stats_region_destroy(&dms->regions[i]); } dm_pool_free(mem, dms->regions); } static int _set_stats_device(struct dm_stats *dms, struct dm_task *dmt) { if (dms->name) return dm_task_set_name(dmt, dms->name); if (dms->uuid) return dm_task_set_uuid(dmt, dms->uuid); if (dms->major > 0) return dm_task_set_major(dmt, dms->major) && dm_task_set_minor(dmt, dms->minor); return_0; } static int _stats_bound(struct dm_stats *dms) { if (dms->major > 0 || dms->name || dms->uuid) return 1; /* %p format specifier expects a void pointer. */ log_debug("Stats handle at %p is not bound.", (void *) dms); return 0; } static void _stats_clear_binding(struct dm_stats *dms) { if (dms->name) dm_pool_free(dms->mem, dms->name); if (dms->uuid) dm_pool_free(dms->mem, dms->uuid); dms->name = dms->uuid = NULL; dms->major = dms->minor = -1; } int dm_stats_bind_devno(struct dm_stats *dms, int major, int minor) { _stats_clear_binding(dms); _stats_regions_destroy(dms); dms->major = major; dms->minor = minor; return 1; } int dm_stats_bind_name(struct dm_stats *dms, const char *name) { _stats_clear_binding(dms); _stats_regions_destroy(dms); if (!(dms->name = dm_pool_strdup(dms->mem, name))) return_0; return 1; } int dm_stats_bind_uuid(struct dm_stats *dms, const char *uuid) { _stats_clear_binding(dms); _stats_regions_destroy(dms); if (!(dms->uuid = dm_pool_strdup(dms->mem, uuid))) return_0; return 1; } static int _stats_check_precise_timestamps(const struct dm_stats *dms) { /* Already checked? */ if (dms && dms->precise) return 1; return dm_message_supports_precise_timestamps(); } int dm_stats_driver_supports_precise(void) { return _stats_check_precise_timestamps(NULL); } int dm_stats_driver_supports_histogram(void) { return _stats_check_precise_timestamps(NULL); } static int _fill_hist_arg(char *hist_arg, size_t hist_len, uint64_t scale, struct dm_histogram *bounds) { int i, l, len = 0, nr_bins; char *arg = hist_arg; uint64_t value; nr_bins = bounds->nr_bins; for (i = 0; i < nr_bins; i++) { value = bounds->bins[i].upper / scale; if ((l = dm_snprintf(arg, hist_len - len, FMTu64"%s", value, (i == (nr_bins - 1)) ? "" : ",")) < 0) return_0; len += l; arg += l; } return 1; } static void *_get_hist_arg(struct dm_histogram *bounds, uint64_t scale, size_t *len) { struct dm_histogram_bin *entry, *bins; size_t hist_len = 1; /* terminating '\0' */ double value; entry = bins = bounds->bins; entry += bounds->nr_bins - 1; while(entry >= bins) { value = (double) (entry--)->upper; /* Use lround to avoid size_t -> double cast warning. */ hist_len += 1 + (size_t) lround(log10(value / scale)); if (entry != bins) hist_len++; /* ',' */ } *len = hist_len; return dm_zalloc(hist_len); } static char *_build_histogram_arg(struct dm_histogram *bounds, int *precise) { struct dm_histogram_bin *entry, *bins; size_t hist_len; char *hist_arg; uint64_t scale; entry = bins = bounds->bins; /* Empty histogram is invalid. */ if (!bounds->nr_bins) { log_error("Cannot format empty histogram description."); return NULL; } /* Validate entries and set *precise if precision < 1ms. */ entry += bounds->nr_bins - 1; while (entry >= bins) { if (entry != bins) { if (entry->upper < (entry - 1)->upper) { log_error("Histogram boundaries must be in " "order of increasing magnitude."); return 0; } } /* * Only enable precise_timestamps automatically if any * value in the histogram bounds uses precision < 1ms. */ if (((entry--)->upper % NSEC_PER_MSEC) && !*precise) *precise = 1; } scale = (*precise) ? 1 : NSEC_PER_MSEC; /* Calculate hist_len and allocate a character buffer. */ if (!(hist_arg = _get_hist_arg(bounds, scale, &hist_len))) { log_error("Could not allocate memory for histogram argument."); return 0; } /* Fill hist_arg with boundary strings. */ if (!_fill_hist_arg(hist_arg, hist_len, scale, bounds)) goto_bad; return hist_arg; bad: log_error("Could not build histogram arguments."); dm_free(hist_arg); return NULL; } static struct dm_task *_stats_send_message(struct dm_stats *dms, char *msg) { struct dm_task *dmt; if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) return_0; if (!_set_stats_device(dms, dmt)) goto_bad; if (!dm_task_set_message(dmt, msg)) goto_bad; if (!dm_task_run(dmt)) goto_bad; return dmt; bad: dm_task_destroy(dmt); return NULL; } /* * Parse a histogram specification returned by the kernel in a * @stats_list response. */ static int _stats_parse_histogram_spec(struct dm_stats *dms, struct dm_stats_region *region, const char *histogram) { static const char *_valid_chars = "0123456789,"; uint64_t scale = region->timescale, this_val = 0; struct dm_pool *mem = dms->hist_mem; struct dm_histogram_bin cur; struct dm_histogram hist; int nr_bins = 1; const char *c, *v, *val_start; char *p, *endptr = NULL; /* Advance past "histogram:". */ histogram = strchr(histogram, ':'); if (!histogram) { log_error("Could not parse histogram description."); return 0; } histogram++; /* @stats_list rows are newline terminated. */ if ((p = strchr(histogram, '\n'))) *p = '\0'; if (!dm_pool_begin_object(mem, sizeof(cur))) return_0; memset(&hist, 0, sizeof(hist)); hist.nr_bins = 0; /* fix later */ hist.region = region; hist.dms = dms; if (!dm_pool_grow_object(mem, &hist, sizeof(hist))) goto_bad; c = histogram; do { for (v = _valid_chars; *v; v++) if (*c == *v) break; if (!*v) { stack; goto badchar; } if (*c == ',') { log_error("Invalid histogram description: %s", histogram); goto bad; } else { val_start = c; endptr = NULL; this_val = strtoull(val_start, &endptr, 10); if (!endptr) { log_error("Could not parse histogram boundary."); goto bad; } c = endptr; /* Advance to units, comma, or end. */ if (*c == ',') c++; else if (*c || (*c == ' ')) { /* Expected ',' or NULL. */ stack; goto badchar; } if (*c == ',') c++; cur.upper = scale * this_val; cur.count = 0; if (!dm_pool_grow_object(mem, &cur, sizeof(cur))) goto_bad; nr_bins++; } } while (*c && (*c != ' ')); /* final upper bound. */ cur.upper = UINT64_MAX; if (!dm_pool_grow_object(mem, &cur, sizeof(cur))) goto_bad; region->bounds = dm_pool_end_object(mem); if (!region->bounds) return_0; region->bounds->nr_bins = nr_bins; log_debug("Added region histogram spec with %d entries.", nr_bins); return 1; badchar: log_error("Invalid character in histogram: '%c' (0x%x)", *c, *c); bad: dm_pool_abandon_object(mem); return 0; } static int _stats_parse_list_region(struct dm_stats *dms, struct dm_stats_region *region, char *line) { char *p = NULL, string_data[4096]; /* FIXME: add dm_sscanf with %ms? */ const char *program_id, *aux_data, *stats_args; const char *empty_string = ""; int r; memset(string_data, 0, sizeof(string_data)); /* * Parse fixed fields, line format: * * : + * * Maximum string data size is 4096 - 1 bytes. */ r = sscanf(line, FMTu64 ": " FMTu64 "+" FMTu64 " " FMTu64 " %4095c", ®ion->region_id, ®ion->start, ®ion->len, ®ion->step, string_data); if (r != 5) return_0; /* program_id is guaranteed to be first. */ program_id = string_data; /* * FIXME: support embedded '\ ' in string data: * s/strchr/_find_unescaped_space()/ */ if ((p = strchr(string_data, ' '))) { /* terminate program_id string. */ *p = '\0'; if (!strcmp(program_id, "-")) program_id = empty_string; aux_data = p + 1; if ((p = strchr(aux_data, ' '))) { /* terminate aux_data string. */ *p = '\0'; if (!strcmp(aux_data, "-")) aux_data = empty_string; stats_args = p + 1; } else stats_args = empty_string; } else aux_data = stats_args = empty_string; if (strstr(stats_args, PRECISE_ARG)) region->timescale = 1; else region->timescale = NSEC_PER_MSEC; if ((p = strstr(stats_args, HISTOGRAM_ARG))) { if (!_stats_parse_histogram_spec(dms, region, p)) return_0; } else region->bounds = NULL; if (!(region->program_id = dm_strdup(program_id))) return_0; if (!(region->aux_data = dm_strdup(aux_data))) { dm_free(region->program_id); return_0; } region->counters = NULL; return 1; } static int _stats_parse_list(struct dm_stats *dms, const char *resp) { uint64_t max_region = 0, nr_regions = 0; struct dm_stats_region cur, fill; struct dm_pool *mem = dms->mem; FILE *list_rows; /* FIXME: use correct maximum line length for kernel format */ char line[256]; if (!resp) { log_error("Could not parse NULL @stats_list response."); return 0; } if (dms->regions) _stats_regions_destroy(dms); /* no regions */ if (!strlen(resp)) { dms->nr_regions = dms->max_region = 0; dms->regions = NULL; return 1; } /* * dm_task_get_message_response() returns a 'const char *' but * since fmemopen also permits "w" it expects a 'char *'. */ if (!(list_rows = fmemopen((char *)resp, strlen(resp), "r"))) return_0; if (!dm_pool_begin_object(mem, 1024)) goto_bad; while(fgets(line, sizeof(line), list_rows)) { if (!_stats_parse_list_region(dms, &cur, line)) goto_bad; /* handle holes in the list of region_ids */ if (cur.region_id > max_region) { memset(&fill, 0, sizeof(fill)); fill.region_id = DM_STATS_REGION_NOT_PRESENT; do { if (!dm_pool_grow_object(mem, &fill, sizeof(fill))) goto_bad; } while (max_region++ < (cur.region_id - 1)); } if (!dm_pool_grow_object(mem, &cur, sizeof(cur))) goto_bad; max_region++; nr_regions++; } dms->nr_regions = nr_regions; dms->max_region = max_region - 1; dms->regions = dm_pool_end_object(mem); if (fclose(list_rows)) stack; return 1; bad: if (fclose(list_rows)) stack; dm_pool_abandon_object(mem); return 0; } int dm_stats_list(struct dm_stats *dms, const char *program_id) { struct dm_task *dmt; char msg[256]; int r; if (!_stats_bound(dms)) return_0; /* allow zero-length program_id for list */ if (!program_id) program_id = dms->program_id; r = dm_snprintf(msg, sizeof(msg), "@stats_list %s", program_id); if (r < 0) { log_error("Failed to prepare stats message."); return 0; } if (!(dmt = _stats_send_message(dms, msg))) return_0; if (!_stats_parse_list(dms, dm_task_get_message_response(dmt))) { log_error("Could not parse @stats_list response."); goto bad; } dm_task_destroy(dmt); return 1; bad: dm_task_destroy(dmt); return 0; } /* * Parse histogram data returned from a @stats_print operation. */ static int _stats_parse_histogram(struct dm_pool *mem, char *hist_str, struct dm_histogram **histogram, struct dm_stats_region *region) { struct dm_histogram hist, *bounds = region->bounds; static const char *_valid_chars = "0123456789:"; int nr_bins = region->bounds->nr_bins; const char *c, *v, *val_start; struct dm_histogram_bin cur; uint64_t sum = 0, this_val; char *endptr = NULL; int bin = 0; c = hist_str; if (!dm_pool_begin_object(mem, sizeof(cur))) return_0; hist.nr_bins = nr_bins; if (!dm_pool_grow_object(mem, &hist, sizeof(hist))) goto_bad; do { memset(&cur, 0, sizeof(cur)); for (v = _valid_chars; *v; v++) if (*c == *v) break; if (!*v) goto badchar; if (*c == ',') goto badchar; else { val_start = c; endptr = NULL; this_val = strtoull(val_start, &endptr, 10); if (!endptr) { log_error("Could not parse histogram value."); goto bad; } c = endptr; /* Advance to colon, or end. */ if (*c == ':') c++; else if (*c & (*c != '\n')) /* Expected ':', '\n', or NULL. */ goto badchar; if (*c == ':') c++; cur.upper = bounds->bins[bin].upper; cur.count = this_val; sum += this_val; if (!dm_pool_grow_object(mem, &cur, sizeof(cur))) goto_bad; bin++; } } while (*c && (*c != '\n')); log_debug("Added region histogram data with %d entries.", nr_bins); *histogram = dm_pool_end_object(mem); (*histogram)->sum = sum; return 1; badchar: log_error("Invalid character in histogram data: '%c' (0x%x)", *c, *c); bad: dm_pool_abandon_object(mem); return 0; } static int _stats_parse_region(struct dm_stats *dms, const char *resp, struct dm_stats_region *region, uint64_t timescale) { struct dm_histogram *hist = NULL; struct dm_pool *mem = dms->mem; struct dm_stats_counters cur; FILE *stats_rows = NULL; uint64_t start = 0, len = 0; char row[256]; int r; if (!resp) { log_error("Could not parse empty @stats_print response."); return 0; } region->start = UINT64_MAX; if (!dm_pool_begin_object(mem, 512)) goto_bad; /* * dm_task_get_message_response() returns a 'const char *' but * since fmemopen also permits "w" it expects a 'char *'. */ stats_rows = fmemopen((char *)resp, strlen(resp), "r"); if (!stats_rows) goto_bad; /* * Output format for each step-sized area of a region: * * + counters * * The first 11 counters have the same meaning as * /sys/block/ * /stat or /proc/diskstats. * * Please refer to Documentation/iostats.txt for details. * * 1. the number of reads completed * 2. the number of reads merged * 3. the number of sectors read * 4. the number of milliseconds spent reading * 5. the number of writes completed * 6. the number of writes merged * 7. the number of sectors written * 8. the number of milliseconds spent writing * 9. the number of I/Os currently in progress * 10. the number of milliseconds spent doing I/Os * 11. the weighted number of milliseconds spent doing I/Os * * Additional counters: * 12. the total time spent reading in milliseconds * 13. the total time spent writing in milliseconds * */ while (fgets(row, sizeof(row), stats_rows)) { r = sscanf(row, FMTu64 "+" FMTu64 /* start+len */ /* reads */ FMTu64 " " FMTu64 " " FMTu64 " " FMTu64 " " /* writes */ FMTu64 " " FMTu64 " " FMTu64 " " FMTu64 " " /* in flight & io nsecs */ FMTu64 " " FMTu64 " " FMTu64 " " /* tot read/write nsecs */ FMTu64 " " FMTu64, &start, &len, &cur.reads, &cur.reads_merged, &cur.read_sectors, &cur.read_nsecs, &cur.writes, &cur.writes_merged, &cur.write_sectors, &cur.write_nsecs, &cur.io_in_progress, &cur.io_nsecs, &cur.weighted_io_nsecs, &cur.total_read_nsecs, &cur.total_write_nsecs); if (r != 15) { log_error("Could not parse @stats_print row."); goto bad; } /* scale time values up if needed */ if (timescale != 1) { cur.read_nsecs *= timescale; cur.write_nsecs *= timescale; cur.io_nsecs *= timescale; cur.weighted_io_nsecs *= timescale; cur.total_read_nsecs *= timescale; cur.total_write_nsecs *= timescale; } if (region->bounds) { /* Find first histogram separator. */ char *hist_str = strchr(row, ':'); if (!hist_str) { log_error("Could not parse histogram value."); goto bad; } /* Find space preceding histogram. */ while (hist_str && *(hist_str - 1) != ' ') hist_str--; /* Use a separate pool for histogram objects since we * are growing the area table and each area's histogram * table simultaneously. */ if (!_stats_parse_histogram(dms->hist_mem, hist_str, &hist, region)) goto_bad; hist->dms = dms; hist->region = region; } cur.histogram = hist; if (!dm_pool_grow_object(mem, &cur, sizeof(cur))) goto_bad; if (region->start == UINT64_MAX) { region->start = start; region->step = len; /* area size is always uniform. */ } } region->len = (start + len) - region->start; region->timescale = timescale; region->counters = dm_pool_end_object(mem); if (fclose(stats_rows)) stack; return 1; bad: if (stats_rows) if (fclose(stats_rows)) stack; dm_pool_abandon_object(mem); return 0; } static void _stats_walk_next(const struct dm_stats *dms, int region, uint64_t *cur_r, uint64_t *cur_a) { struct dm_stats_region *cur = NULL; int present; if (!dms || !dms->regions) return; cur = &dms->regions[*cur_r]; present = _stats_region_present(cur); if (region && present) *cur_a = _nr_areas_region(cur); if (region || !present || ++(*cur_a) == _nr_areas_region(cur)) { *cur_a = 0; while(!dm_stats_region_present(dms, ++(*cur_r)) && *cur_r < dms->max_region) ; /* keep walking until a present region is found * or the end of the table is reached. */ } } static void _stats_walk_start(const struct dm_stats *dms, uint64_t *cur_r, uint64_t *cur_a) { if (!dms || !dms->regions) return; *cur_r = 0; *cur_a = 0; /* advance to the first present region */ if (!dm_stats_region_present(dms, dms->cur_region)) _stats_walk_next(dms, 0, cur_r, cur_a); } void dm_stats_walk_start(struct dm_stats *dms) { _stats_walk_start(dms, &dms->cur_region, &dms->cur_area); } void dm_stats_walk_next(struct dm_stats *dms) { _stats_walk_next(dms, 0, &dms->cur_region, &dms->cur_area); } void dm_stats_walk_next_region(struct dm_stats *dms) { _stats_walk_next(dms, 1, &dms->cur_region, &dms->cur_area); } static int _stats_walk_end(const struct dm_stats *dms, uint64_t *cur_r, uint64_t *cur_a) { struct dm_stats_region *region = NULL; int end = 0; if (!dms || !dms->regions) return 1; region = &dms->regions[*cur_r]; end = (*cur_r > dms->max_region || (*cur_r == dms->max_region && *cur_a >= _nr_areas_region(region))); return end; } int dm_stats_walk_end(struct dm_stats *dms) { return _stats_walk_end(dms, &dms->cur_region, &dms->cur_area); } uint64_t dm_stats_get_region_nr_areas(const struct dm_stats *dms, uint64_t region_id) { struct dm_stats_region *region = &dms->regions[region_id]; return _nr_areas_region(region); } uint64_t dm_stats_get_current_nr_areas(const struct dm_stats *dms) { return dm_stats_get_region_nr_areas(dms, dms->cur_region); } uint64_t dm_stats_get_nr_areas(const struct dm_stats *dms) { uint64_t nr_areas = 0; /* use a separate cursor */ uint64_t cur_region, cur_area; _stats_walk_start(dms, &cur_region, &cur_area); do { nr_areas += dm_stats_get_current_nr_areas(dms); _stats_walk_next(dms, 1, &cur_region, &cur_area); } while (!_stats_walk_end(dms, &cur_region, &cur_area)); return nr_areas; } int dm_stats_get_region_nr_histogram_bins(const struct dm_stats *dms, uint64_t region_id) { region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; if (!dms->regions[region_id].bounds) return_0; return dms->regions[region_id].bounds->nr_bins; } static int _stats_create_region(struct dm_stats *dms, uint64_t *region_id, uint64_t start, uint64_t len, int64_t step, int precise, const char *hist_arg, const char *program_id, const char *aux_data) { const char *err_fmt = "Could not prepare @stats_create %s."; const char *precise_str = PRECISE_ARG; const char *resp, *opt_args = NULL; char msg[1024], range[64], *endptr = NULL; struct dm_task *dmt = NULL; int r = 0, nr_opt = 0; if (!_stats_bound(dms)) return_0; if (!program_id || !strlen(program_id)) program_id = dms->program_id; if (start || len) { if (!dm_snprintf(range, sizeof(range), FMTu64 "+" FMTu64, start, len)) { log_error(err_fmt, "range"); return 0; } } if (precise < 0) precise = dms->precise; if (precise) nr_opt++; else precise_str = ""; if (hist_arg) nr_opt++; else hist_arg = ""; if (nr_opt) { if ((dm_asprintf((char **)&opt_args, "%d %s %s%s", nr_opt, precise_str, (strlen(hist_arg)) ? HISTOGRAM_ARG : "", hist_arg)) < 0) { log_error(err_fmt, PRECISE_ARG " option."); return 0; } } else opt_args = dm_strdup(""); if (!dm_snprintf(msg, sizeof(msg), "@stats_create %s %s" FMTu64 " %s %s %s", (start || len) ? range : "-", (step < 0) ? "/" : "", (uint64_t)llabs(step), opt_args, program_id, aux_data)) { log_error(err_fmt, "message"); dm_free((void *) opt_args); return 0; } if (!(dmt = _stats_send_message(dms, msg))) goto_out; resp = dm_task_get_message_response(dmt); if (!resp) { log_error("Could not parse empty @stats_create response."); goto out; } if (region_id) { *region_id = strtoull(resp, &endptr, 10); if (resp == endptr) goto_out; } r = 1; out: if (dmt) dm_task_destroy(dmt); dm_free((void *) opt_args); return r; } int dm_stats_create_region(struct dm_stats *dms, uint64_t *region_id, uint64_t start, uint64_t len, int64_t step, int precise, struct dm_histogram *bounds, const char *program_id, const char *aux_data) { char *hist_arg = NULL; int r = 0; /* Nanosecond counters and histograms both need precise_timestamps. */ if ((precise || bounds) && !_stats_check_precise_timestamps(dms)) return_0; if (bounds) { /* _build_histogram_arg enables precise if vals < 1ms. */ if (!(hist_arg = _build_histogram_arg(bounds, &precise))) goto_out; } r = _stats_create_region(dms, region_id, start, len, step, precise, hist_arg, program_id, aux_data); dm_free(hist_arg); out: return r; } int dm_stats_delete_region(struct dm_stats *dms, uint64_t region_id) { struct dm_task *dmt; char msg[1024]; if (!_stats_bound(dms)) return_0; if (!dm_snprintf(msg, sizeof(msg), "@stats_delete " FMTu64, region_id)) { log_error("Could not prepare @stats_delete message."); return 0; } dmt = _stats_send_message(dms, msg); if (!dmt) return_0; dm_task_destroy(dmt); return 1; } int dm_stats_clear_region(struct dm_stats *dms, uint64_t region_id) { struct dm_task *dmt; char msg[1024]; if (!_stats_bound(dms)) return_0; if (!dm_snprintf(msg, sizeof(msg), "@stats_clear " FMTu64, region_id)) { log_error("Could not prepare @stats_clear message."); return 0; } dmt = _stats_send_message(dms, msg); if (!dmt) return_0; dm_task_destroy(dmt); return 1; } static struct dm_task *_stats_print_region(struct dm_stats *dms, uint64_t region_id, unsigned start_line, unsigned num_lines, unsigned clear) { /* @stats_print[_clear] [ ] */ const char *err_fmt = "Could not prepare @stats_print %s."; struct dm_task *dmt = NULL; char msg[1024], lines[64]; if (start_line || num_lines) if (!dm_snprintf(lines, sizeof(lines), "%u %u", start_line, num_lines)) { log_error(err_fmt, "row specification"); return NULL; } if (!dm_snprintf(msg, sizeof(msg), "@stats_print%s " FMTu64 " %s", (clear) ? "_clear" : "", region_id, (start_line || num_lines) ? lines : "")) { log_error(err_fmt, "message"); return NULL; } if (!(dmt = _stats_send_message(dms, msg))) return_NULL; return dmt; } char *dm_stats_print_region(struct dm_stats *dms, uint64_t region_id, unsigned start_line, unsigned num_lines, unsigned clear) { char *resp = NULL; struct dm_task *dmt = NULL; const char *response; if (!_stats_bound(dms)) return_0; dmt = _stats_print_region(dms, region_id, start_line, num_lines, clear); if (!dmt) return_0; if (!(response = dm_task_get_message_response(dmt))) goto_out; if (!(resp = dm_pool_strdup(dms->mem, response))) log_error("Could not allocate memory for response buffer."); out: dm_task_destroy(dmt); return resp; } void dm_stats_buffer_destroy(struct dm_stats *dms, char *buffer) { dm_pool_free(dms->mem, buffer); } uint64_t dm_stats_get_nr_regions(const struct dm_stats *dms) { if (!dms || !dms->regions) return_0; return dms->nr_regions; } /** * Test whether region_id is present in this set of stats data */ int dm_stats_region_present(const struct dm_stats *dms, uint64_t region_id) { if (!dms->regions) return_0; if (region_id > dms->max_region) return_0; return _stats_region_present(&dms->regions[region_id]); } static int _dm_stats_populate_region(struct dm_stats *dms, uint64_t region_id, const char *resp) { struct dm_stats_region *region = &dms->regions[region_id]; if (!_stats_bound(dms)) return_0; if (!_stats_parse_region(dms, resp, region, region->timescale)) { log_error("Could not parse @stats_print message response."); return 0; } region->region_id = region_id; return 1; } int dm_stats_populate(struct dm_stats *dms, const char *program_id, uint64_t region_id) { int all_regions = (region_id == DM_STATS_REGIONS_ALL); struct dm_task *dmt = NULL; /* @stats_print task */ const char *resp; if (!_stats_bound(dms)) return_0; /* allow zero-length program_id for populate */ if (!program_id) program_id = dms->program_id; if (all_regions && !dm_stats_list(dms, program_id)) { log_error("Could not parse @stats_list response."); goto bad; } /* successful list but no regions registered */ if (!dms->nr_regions) return_0; dm_stats_walk_start(dms); do { region_id = (all_regions) ? dm_stats_get_current_region(dms) : region_id; /* obtain all lines and clear counter values */ if (!(dmt = _stats_print_region(dms, region_id, 0, 0, 1))) goto_bad; resp = dm_task_get_message_response(dmt); if (!_dm_stats_populate_region(dms, region_id, resp)) { dm_task_destroy(dmt); goto_bad; } dm_task_destroy(dmt); dm_stats_walk_next_region(dms); } while (all_regions && !dm_stats_walk_end(dms)); return 1; bad: _stats_regions_destroy(dms); dms->regions = NULL; return 0; } /** * destroy a dm_stats object and all associated regions and counter sets. */ void dm_stats_destroy(struct dm_stats *dms) { _stats_regions_destroy(dms); _stats_clear_binding(dms); dm_pool_destroy(dms->mem); dm_pool_destroy(dms->hist_mem); dm_free(dms->program_id); dm_free(dms); } /** * Methods for accessing counter fields. All methods share the * following naming scheme and prototype: * * uint64_t dm_stats_get_COUNTER(struct dm_stats *, uint64_t, uint64_t) * * Where the two integer arguments are the region_id and area_id * respectively. */ #define MK_STATS_GET_COUNTER_FN(counter) \ uint64_t dm_stats_get_ ## counter(const struct dm_stats *dms, \ uint64_t region_id, uint64_t area_id) \ { \ region_id = (region_id == DM_STATS_REGION_CURRENT) \ ? dms->cur_region : region_id ; \ area_id = (area_id == DM_STATS_REGION_CURRENT) \ ? dms->cur_area : area_id ; \ return dms->regions[region_id].counters[area_id].counter; \ } MK_STATS_GET_COUNTER_FN(reads) MK_STATS_GET_COUNTER_FN(reads_merged) MK_STATS_GET_COUNTER_FN(read_sectors) MK_STATS_GET_COUNTER_FN(read_nsecs) MK_STATS_GET_COUNTER_FN(writes) MK_STATS_GET_COUNTER_FN(writes_merged) MK_STATS_GET_COUNTER_FN(write_sectors) MK_STATS_GET_COUNTER_FN(write_nsecs) MK_STATS_GET_COUNTER_FN(io_in_progress) MK_STATS_GET_COUNTER_FN(io_nsecs) MK_STATS_GET_COUNTER_FN(weighted_io_nsecs) MK_STATS_GET_COUNTER_FN(total_read_nsecs) MK_STATS_GET_COUNTER_FN(total_write_nsecs) #undef MK_STATS_GET_COUNTER_FN int dm_stats_get_rd_merges_per_sec(const struct dm_stats *dms, double *rrqm, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *rrqm = ((double) c->reads_merged) / (double) dms->interval_ns; return 1; } int dm_stats_get_wr_merges_per_sec(const struct dm_stats *dms, double *wrqm, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *wrqm = ((double) c->writes_merged) / (double) dms->interval_ns; return 1; } int dm_stats_get_reads_per_sec(const struct dm_stats *dms, double *rd_s, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *rd_s = ((double) c->reads * NSEC_PER_SEC) / (double) dms->interval_ns; return 1; } int dm_stats_get_writes_per_sec(const struct dm_stats *dms, double *wr_s, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *wr_s = ((double) c->writes * (double) NSEC_PER_SEC) / (double) dms->interval_ns; return 1; } int dm_stats_get_read_sectors_per_sec(const struct dm_stats *dms, double *rsec_s, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *rsec_s = ((double) c->read_sectors * (double) NSEC_PER_SEC) / (double) dms->interval_ns; return 1; } int dm_stats_get_write_sectors_per_sec(const struct dm_stats *dms, double *wsec_s, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *wsec_s = ((double) c->write_sectors * (double) NSEC_PER_SEC) / (double) dms->interval_ns; return 1; } int dm_stats_get_average_request_size(const struct dm_stats *dms, double *arqsz, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; uint64_t nr_ios, nr_sectors; if (!dms->interval_ns) return_0; *arqsz = 0.0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); nr_ios = c->reads + c->writes; nr_sectors = c->read_sectors + c->write_sectors; if (nr_ios) *arqsz = (double) nr_sectors / (double) nr_ios; return 1; } int dm_stats_get_average_queue_size(const struct dm_stats *dms, double *qusz, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; uint64_t io_ticks; if (!dms->interval_ns) return_0; *qusz = 0.0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); io_ticks = c->weighted_io_nsecs; if (io_ticks) *qusz = (double) io_ticks / (double) dms->interval_ns; return 1; } int dm_stats_get_average_wait_time(const struct dm_stats *dms, double *await, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; uint64_t io_ticks, nr_ios; if (!dms->interval_ns) return_0; *await = 0.0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); io_ticks = c->read_nsecs + c->write_nsecs; nr_ios = c->reads + c->writes; if (nr_ios) *await = (double) io_ticks / (double) nr_ios; return 1; } int dm_stats_get_average_rd_wait_time(const struct dm_stats *dms, double *await, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; uint64_t rd_io_ticks, nr_rd_ios; if (!dms->interval_ns) return_0; *await = 0.0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); rd_io_ticks = c->read_nsecs; nr_rd_ios = c->reads; if (rd_io_ticks) *await = (double) rd_io_ticks / (double) nr_rd_ios; return 1; } int dm_stats_get_average_wr_wait_time(const struct dm_stats *dms, double *await, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; uint64_t wr_io_ticks, nr_wr_ios; if (!dms->interval_ns) return_0; *await = 0.0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); wr_io_ticks = c->write_nsecs; nr_wr_ios = c->writes; if (wr_io_ticks && nr_wr_ios) *await = (double) wr_io_ticks / (double) nr_wr_ios; return 1; } int dm_stats_get_service_time(const struct dm_stats *dms, double *svctm, uint64_t region_id, uint64_t area_id) { dm_percent_t util; double tput; if (!dm_stats_get_throughput(dms, &tput, region_id, area_id)) return_0; if (!dm_stats_get_utilization(dms, &util, region_id, area_id)) return_0; /* avoid NAN with zero counter values */ if ( (uint64_t) tput == 0 || (uint64_t) util == 0) { *svctm = 0.0; return 1; } *svctm = ((double) NSEC_PER_SEC * dm_percent_to_float(util)) / (100.0 * tput); return 1; } int dm_stats_get_throughput(const struct dm_stats *dms, double *tput, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); *tput = (( NSEC_PER_SEC * ((double) c->reads + (double) c->writes)) / (double) (dms->interval_ns)); return 1; } int dm_stats_get_utilization(const struct dm_stats *dms, dm_percent_t *util, uint64_t region_id, uint64_t area_id) { struct dm_stats_counters *c; uint64_t io_nsecs; if (!dms->interval_ns) return_0; region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_REGION_CURRENT) ? dms->cur_area : area_id ; c = &(dms->regions[region_id].counters[area_id]); /** * If io_nsec > interval_ns there is something wrong with the clock * for the last interval; do not allow a value > 100% utilization * to be passed to a dm_make_percent() call. We expect to see these * at startup if counters have not been cleared before the first read. */ io_nsecs = (c->io_nsecs <= dms->interval_ns) ? c->io_nsecs : dms->interval_ns; *util = dm_make_percent(io_nsecs, dms->interval_ns); return 1; } void dm_stats_set_sampling_interval_ms(struct dm_stats *dms, uint64_t interval_ms) { /* All times use nsecs internally. */ dms->interval_ns = interval_ms * NSEC_PER_MSEC; } void dm_stats_set_sampling_interval_ns(struct dm_stats *dms, uint64_t interval_ns) { dms->interval_ns = interval_ns; } uint64_t dm_stats_get_sampling_interval_ms(const struct dm_stats *dms) { /* All times use nsecs internally. */ return (dms->interval_ns / NSEC_PER_MSEC); } uint64_t dm_stats_get_sampling_interval_ns(const struct dm_stats *dms) { /* All times use nsecs internally. */ return (dms->interval_ns); } int dm_stats_set_program_id(struct dm_stats *dms, int allow_empty, const char *program_id) { if (!allow_empty && (!program_id || !strlen(program_id))) { log_error("Empty program_id not permitted without " "allow_empty=1"); return 0; } if (!program_id) program_id = ""; if (dms->program_id) dm_free(dms->program_id); if (!(dms->program_id = dm_strdup(program_id))) return_0; return 1; } uint64_t dm_stats_get_current_region(const struct dm_stats *dms) { return dms->cur_region; } uint64_t dm_stats_get_current_area(const struct dm_stats *dms) { return dms->cur_area; } int dm_stats_get_region_start(const struct dm_stats *dms, uint64_t *start, uint64_t region_id) { if (!dms || !dms->regions) return_0; *start = dms->regions[region_id].start; return 1; } int dm_stats_get_region_len(const struct dm_stats *dms, uint64_t *len, uint64_t region_id) { if (!dms || !dms->regions) return_0; *len = dms->regions[region_id].len; return 1; } int dm_stats_get_region_area_len(const struct dm_stats *dms, uint64_t *len, uint64_t region_id) { if (!dms || !dms->regions) return_0; *len = dms->regions[region_id].step; return 1; } int dm_stats_get_current_region_start(const struct dm_stats *dms, uint64_t *start) { return dm_stats_get_region_start(dms, start, dms->cur_region); } int dm_stats_get_current_region_len(const struct dm_stats *dms, uint64_t *len) { return dm_stats_get_region_len(dms, len, dms->cur_region); } int dm_stats_get_current_region_area_len(const struct dm_stats *dms, uint64_t *step) { return dm_stats_get_region_area_len(dms, step, dms->cur_region); } int dm_stats_get_area_start(const struct dm_stats *dms, uint64_t *start, uint64_t region_id, uint64_t area_id) { struct dm_stats_region *region; if (!dms || !dms->regions) return_0; region = &dms->regions[region_id]; *start = region->start + region->step * area_id; return 1; } int dm_stats_get_area_offset(const struct dm_stats *dms, uint64_t *offset, uint64_t region_id, uint64_t area_id) { if (!dms || !dms->regions) return_0; *offset = dms->regions[region_id].step * area_id; return 1; } int dm_stats_get_current_area_start(const struct dm_stats *dms, uint64_t *start) { return dm_stats_get_area_start(dms, start, dms->cur_region, dms->cur_area); } int dm_stats_get_current_area_offset(const struct dm_stats *dms, uint64_t *offset) { return dm_stats_get_area_offset(dms, offset, dms->cur_region, dms->cur_area); } int dm_stats_get_current_area_len(const struct dm_stats *dms, uint64_t *len) { return dm_stats_get_region_area_len(dms, len, dms->cur_region); } const char *dm_stats_get_region_program_id(const struct dm_stats *dms, uint64_t region_id) { const char *program_id = dms->regions[region_id].program_id; return (program_id) ? program_id : ""; } const char *dm_stats_get_region_aux_data(const struct dm_stats *dms, uint64_t region_id) { const char *aux_data = dms->regions[region_id].aux_data; return (aux_data) ? aux_data : "" ; } const char *dm_stats_get_current_region_program_id(const struct dm_stats *dms) { return dm_stats_get_region_program_id(dms, dms->cur_region); } const char *dm_stats_get_current_region_aux_data(const struct dm_stats *dms) { return dm_stats_get_region_aux_data(dms, dms->cur_region); } int dm_stats_get_region_precise_timestamps(const struct dm_stats *dms, uint64_t region_id) { struct dm_stats_region *region = &dms->regions[region_id]; return region->timescale == 1; } int dm_stats_get_current_region_precise_timestamps(const struct dm_stats *dms) { return dm_stats_get_region_precise_timestamps(dms, dms->cur_region); } /* * Histogram access methods. */ struct dm_histogram *dm_stats_get_histogram(const struct dm_stats *dms, uint64_t region_id, uint64_t area_id) { region_id = (region_id == DM_STATS_REGION_CURRENT) ? dms->cur_region : region_id ; area_id = (area_id == DM_STATS_AREA_CURRENT) ? dms->cur_area : area_id ; if (!dms->regions[region_id].counters) return dms->regions[region_id].bounds; return dms->regions[region_id].counters[area_id].histogram; } int dm_histogram_get_nr_bins(const struct dm_histogram *dmh) { return dmh->nr_bins; } uint64_t dm_histogram_get_bin_lower(const struct dm_histogram *dmh, int bin) { return (!bin) ? 0 : dmh->bins[bin - 1].upper; } uint64_t dm_histogram_get_bin_upper(const struct dm_histogram *dmh, int bin) { return dmh->bins[bin].upper; } uint64_t dm_histogram_get_bin_width(const struct dm_histogram *dmh, int bin) { uint64_t upper, lower; upper = dm_histogram_get_bin_upper(dmh, bin); lower = dm_histogram_get_bin_lower(dmh, bin); return (upper - lower); } uint64_t dm_histogram_get_bin_count(const struct dm_histogram *dmh, int bin) { return dmh->bins[bin].count; } uint64_t dm_histogram_get_sum(const struct dm_histogram *dmh) { return dmh->sum; } dm_percent_t dm_histogram_get_bin_percent(const struct dm_histogram *dmh, int bin) { uint64_t value = dm_histogram_get_bin_count(dmh, bin); uint64_t width = dm_histogram_get_bin_width(dmh, bin); uint64_t total = dm_histogram_get_sum(dmh); double val = (double) value; if (!total || !value || !width) return DM_PERCENT_0; return dm_make_percent((uint64_t) val, total); } /* * Histogram string helper functions: used to construct histogram and * bin boundary strings from numeric data. */ /* * Allocate an unbound histogram object with nr_bins bins. Only used * for histograms used to hold bounds values as arguments for calls to * dm_stats_create_region(). */ static struct dm_histogram *_alloc_dm_histogram(int nr_bins) { /* Allocate space for dm_histogram + nr_entries. */ size_t size = sizeof(struct dm_histogram) + (unsigned) nr_bins * sizeof(struct dm_histogram_bin); return dm_zalloc(size); } /* * Parse a histogram bounds string supplied by the user. The string * consists of a list of numbers, "n1,n2,n3,..." with optional 'ns', * 'us', 'ms', or 's' unit suffixes. * * The scale parameter indicates the timescale used for this region: one * for nanoscale resolution and NSEC_PER_MSEC for miliseconds. * * On return bounds contains a pointer to an array of uint64_t * histogram bounds values expressed in units of nanoseconds. */ struct dm_histogram *dm_histogram_bounds_from_string(const char *bounds_str) { static const char *_valid_chars = "0123456789,muns"; uint64_t this_val = 0, mult = 1; const char *c, *v, *val_start; struct dm_histogram_bin *cur; struct dm_histogram *dmh; int nr_entries = 1; char *endptr; c = bounds_str; /* Count number of bounds entries. */ while(*c) if (*(c++) == ',') nr_entries++; c = bounds_str; if (!(dmh = _alloc_dm_histogram(nr_entries))) return_0; dmh->nr_bins = nr_entries; cur = dmh->bins; do { for (v = _valid_chars; *v; v++) if (*c == *v) break; if (!*v) { stack; goto badchar; } if (*c == ',') { log_error("Empty histogram bin not allowed: %s", bounds_str); goto bad; } else { val_start = c; endptr = NULL; this_val = strtoull(val_start, &endptr, 10); if (!endptr) { log_error("Could not parse histogram bound."); goto bad; } c = endptr; /* Advance to units, comma, or end. */ if (*c == 's') { mult = NSEC_PER_SEC; c++; /* Advance over 's'. */ } else if (*(c + 1) == 's') { if (*c == 'm') mult = NSEC_PER_MSEC; else if (*c == 'u') mult = NSEC_PER_USEC; else if (*c == 'n') mult = 1; else { stack; goto badchar; } c += 2; /* Advance over 'ms', 'us', or 'ns'. */ } else if (*c == ',') c++; else if (*c) { /* Expected ',' or NULL. */ stack; goto badchar; } if (*c == ',') c++; this_val *= mult; (cur++)->upper = this_val; } } while (*c); /* Bounds histograms have no owner. */ dmh->dms = NULL; dmh->region = NULL; return dmh; badchar: log_error("Invalid character in histogram: %c", *c); bad: dm_free(dmh); return NULL; } struct dm_histogram *dm_histogram_bounds_from_uint64(const uint64_t *bounds) { const uint64_t *entry = bounds; struct dm_histogram_bin *cur; struct dm_histogram *dmh; int nr_entries = 1; if (!bounds || !bounds[0]) { log_error("Could not parse empty histogram bounds array"); return 0; } /* Count number of bounds entries. */ while(*entry) if (*(++entry)) nr_entries++; entry = bounds; if (!(dmh = _alloc_dm_histogram(nr_entries))) return_0; dmh->nr_bins = nr_entries; cur = dmh->bins; while (*entry) (cur++)->upper = *(entry++); /* Bounds histograms have no owner. */ dmh->dms = NULL; dmh->region = NULL; return dmh; } void dm_histogram_bounds_destroy(struct dm_histogram *bounds) { if (!bounds) return; /* Bounds histograms are not bound to any handle or region. */ if (bounds->dms || bounds->region) { log_error("Freeing invalid histogram bounds pointer %p.", (void *) bounds); stack; } /* dm_free() expects a (void *). */ dm_free((void *) bounds); } /* * Scale a bounds value down from nanoseconds to the largest possible * whole unit suffix. */ static void _scale_bound_value_to_suffix(uint64_t *bound, const char **suffix) { *suffix = "ns"; if (!(*bound % NSEC_PER_SEC)) { *bound /= NSEC_PER_SEC; *suffix = "s"; } else if (!(*bound % NSEC_PER_MSEC)) { *bound /= NSEC_PER_MSEC; *suffix = "ms"; } else if (!(*bound % NSEC_PER_USEC)) { *bound /= NSEC_PER_USEC; *suffix = "us"; } } #define DM_HISTOGRAM_BOUNDS_MASK 0x30 static int _make_bounds_string(char *buf, size_t size, uint64_t lower, uint64_t upper, int flags, int width) { const char *l_suff = NULL; const char *u_suff = NULL; const char *sep = ""; char bound_buf[32]; int bounds = flags & DM_HISTOGRAM_BOUNDS_MASK; if (!bounds) return_0; *buf = '\0'; if (flags & DM_HISTOGRAM_SUFFIX) { _scale_bound_value_to_suffix(&lower, &l_suff); _scale_bound_value_to_suffix(&upper, &u_suff); } else l_suff = u_suff = ""; if (flags & DM_HISTOGRAM_VALUES) sep = ":"; if (bounds > DM_HISTOGRAM_BOUNDS_LOWER) { /* Handle infinite uppermost bound. */ if (upper == UINT64_MAX) { if (dm_snprintf(bound_buf, sizeof(bound_buf), ">" FMTu64 "%s", lower, l_suff) < 0) goto_out; /* Only display an 'upper' string for final bin. */ bounds = DM_HISTOGRAM_BOUNDS_UPPER; } else { if (dm_snprintf(bound_buf, sizeof(bound_buf), FMTu64 "%s", upper, u_suff) < 0) goto_out; } } else if (bounds == DM_HISTOGRAM_BOUNDS_LOWER) { if ((dm_snprintf(bound_buf, sizeof(bound_buf), FMTu64 "%s", lower, l_suff)) < 0) goto_out; } switch (bounds) { case DM_HISTOGRAM_BOUNDS_LOWER: case DM_HISTOGRAM_BOUNDS_UPPER: return dm_snprintf(buf, size, "%*s%s", width, bound_buf, sep); case DM_HISTOGRAM_BOUNDS_RANGE: return dm_snprintf(buf, size, FMTu64 "%s-%s%s", lower, l_suff, bound_buf, sep); } out: return 0; } #define BOUND_WIDTH_NOSUFFIX 10 /* 999999999 nsecs */ #define BOUND_WIDTH 6 /* bounds string up to 9999xs */ #define COUNT_WIDTH 6 /* count string: up to 9999 */ #define PERCENT_WIDTH 6 /* percent string : 0.00-100.00% */ #define DM_HISTOGRAM_VALUES_MASK 0x06 const char *dm_histogram_to_string(const struct dm_histogram *dmh, int bin, int width, int flags) { int minwidth, bounds, values, start, last; uint64_t lower, upper, val_u64; /* bounds of the current bin. */ /* Use the histogram pool for string building. */ struct dm_pool *mem = dmh->dms->hist_mem; char buf[64], bounds_buf[64]; const char *sep = ""; int bounds_width; ssize_t len = 0; float val_flt; bounds = flags & DM_HISTOGRAM_BOUNDS_MASK; values = flags & DM_HISTOGRAM_VALUES; if (bin < 0) { start = 0; last = dmh->nr_bins - 1; } else start = last = bin; minwidth = width; if (width < 0 || !values) width = minwidth = 0; /* no padding */ else if (flags & DM_HISTOGRAM_PERCENT) width = minwidth = (width) ? : PERCENT_WIDTH; else if (flags & DM_HISTOGRAM_VALUES) width = minwidth = (width) ? : COUNT_WIDTH; if (values && !width) sep = ":"; /* Set bounds string to the empty string. */ bounds_buf[0] = '\0'; if (!dm_pool_begin_object(mem, 64)) return_0; for (bin = start; bin <= last; bin++) { if (bounds) { /* Default bounds width depends on time suffixes. */ bounds_width = (!(flags & DM_HISTOGRAM_SUFFIX)) ? BOUND_WIDTH_NOSUFFIX : BOUND_WIDTH ; bounds_width = (!width) ? width : bounds_width; lower = dm_histogram_get_bin_lower(dmh, bin); upper = dm_histogram_get_bin_upper(dmh, bin); len = sizeof(bounds_buf); len = _make_bounds_string(bounds_buf, len, lower, upper, flags, bounds_width); /* * Comma separates "bounds: value" pairs unless * --noheadings is used. */ sep = (width || !values) ? "," : ":"; /* Adjust width by real bounds length if set. */ width -= (width) ? (len - (bounds_width + 1)) : 0; /* -ve width indicates specified width was overrun. */ width = (width > 0) ? width : 0; } if (bin == last) sep = ""; if (flags & DM_HISTOGRAM_PERCENT) { dm_percent_t pr; pr = dm_histogram_get_bin_percent(dmh, bin); val_flt = dm_percent_to_float(pr); len = dm_snprintf(buf, sizeof(buf), "%s%*.2f%%%s", bounds_buf, width, val_flt, sep); } else if (values) { val_u64 = dmh->bins[bin].count; len = dm_snprintf(buf, sizeof(buf), "%s%*"PRIu64"%s", bounds_buf, width, val_u64, sep); } else if (bounds) len = dm_snprintf(buf, sizeof(buf), "%s%s", bounds_buf, sep); else { *buf = '\0'; len = 0; } if (len < 0) goto_bad; width = minwidth; /* re-set histogram column width. */ if (!dm_pool_grow_object(mem, buf, (size_t) len)) goto_bad; } if (!dm_pool_grow_object(mem, "\0", 1)) goto_bad; return (const char *) dm_pool_end_object(mem); bad: dm_pool_abandon_object(mem); return NULL; } /* * Backward compatible dm_stats_create_region() implementations. * * Keep these at the end of the file to avoid adding clutter around the * current dm_stats_create_region() version. */ #if defined(__GNUC__) int dm_stats_create_region_v1_02_106(struct dm_stats *dms, uint64_t *region_id, uint64_t start, uint64_t len, int64_t step, int precise, const char *program_id, const char *aux_data); int dm_stats_create_region_v1_02_106(struct dm_stats *dms, uint64_t *region_id, uint64_t start, uint64_t len, int64_t step, int precise, const char *program_id, const char *aux_data) { /* 1.02.106 lacks histogram argument. */ return _stats_create_region(dms, region_id, start, len, step, precise, NULL, program_id, aux_data); } DM_EXPORT_SYMBOL(dm_stats_create_region, 1_02_106); int dm_stats_create_region_v1_02_104(struct dm_stats *dms, uint64_t *region_id, uint64_t start, uint64_t len, int64_t step, const char *program_id, const char *aux_data); int dm_stats_create_region_v1_02_104(struct dm_stats *dms, uint64_t *region_id, uint64_t start, uint64_t len, int64_t step, const char *program_id, const char *aux_data) { /* 1.02.104 lacks histogram and precise arguments. */ return _stats_create_region(dms, region_id, start, len, step, 0, NULL, program_id, aux_data); } DM_EXPORT_SYMBOL(dm_stats_create_region, 1_02_104); #endif